Механизм масштабирования и панорамирования в D3.js основан на
поведении d3.zoom, которое управляет трансформацией
transform для SVG- и DOM-элементов через параметры масштаба
и смещения. Управление границами этих трансформаций реализуется двумя
ключевыми ограничителями: scaleExtent и
translateExtent, которые определяют допустимый диапазон
зума и перемещения сцены.
Параметр scaleExtent задаёт диапазон допустимых значений
масштаба, то есть коэффициента увеличения k в трансформации
transform = translate(x, y) scale(k).
const zoom = d3.zoom()
.scaleExtent([1, 8]);
В данном случае минимальный масштаб равен 1 (отсутствие увеличения), максимальный — 8 (восемькратное увеличение). Любая попытка выйти за эти пределы будет автоматически ограничена поведением zoom-поведения.
Во внутренней модели D3 значение k всегда нормализуется
относительно диапазона:
k_mink_maxЭто ограничение работает на уровне событий wheel,
touchzoom, а также при программном вызове
zoom.scaleTo и zoom.scaleBy.
svg.call(
d3.zoom()
.scaleExtent([0.5, 5])
.on("zoom", (event) => {
g.attr("transform", event.transform);
})
);
Любое значение event.transform.k гарантированно
находится в пределах [0.5, 5].
Ограничение масштаба используется для:
Если scaleExtent ограничивает масштаб, то
translateExtent ограничивает область перемещения
(панорамирования). Это границы, в пределах которых может перемещаться
содержимое сцены.
const zoom = d3.zoom()
.translateExtent([[0, 0], [width, height]]);
Данный параметр задаёт прямоугольник в координатах пространства сцены:
[x0, y0] — верхний левый угол допустимой области[x1, y1] — нижний правый угол допустимой областиКогда применяется трансформация:
transform = d3.zoomIdentity
.translate(x, y)
.scale(k);
D3 вычисляет допустимые границы смещения так, чтобы видимая область
не выходила за пределы translateExtent.
Если пользователь пытается перетащить сцену за границу, значения
x и y корректируются автоматически.
Особенность translateExtent заключается в том, что он
работает в координатах внутренней системы, а не экрана. Поэтому при
масштабировании границы воспринимаются с учётом текущего
scale.
Фактически ограничение вычисляется как:
Это приводит к тому, что при сильном увеличении сцена «сжимается» внутри допустимой области, а при уменьшении — расширяется.
Одновременное использование двух ограничителей формирует замкнутую систему управления камерой:
const zoom = d3.zoom()
.scaleExtent([1, 10])
.translateExtent([[0, 0], [2000, 2000]]);
В этой конфигурации:
При изменении масштаба меняется и «эффективная» область панорамирования. Это связано с тем, что viewport отображает часть сцены, и при увеличении масштаба пользователь видит меньшую часть пространства, но с большей детализацией.
Таким образом:
k → min доступная область перемещения
уменьшаетсяk → max доступная область перемещения
увеличиваетсяПри инициализации zoom важно учитывать начальную трансформацию:
const zoom = d3.zoom()
.scaleExtent([1, 6])
.translateExtent([[0, 0], [width, height]]);
svg.call(zoom);
svg.call(
zoom.transform,
d3.zoomIdentity.translate(0, 0).scale(1)
);
Если начальная трансформация не соответствует допустимым границам, D3 автоматически корректирует её в сторону ближайшего валидного значения.
D3 предоставляет методы, которые также подчиняются ограничениям:
zoom.scaleTo(svg, 3);
zoom.translateTo(svg, 100, 100);
Если значения выходят за пределы:
scaleTo будет ограничен scaleExtenttranslateTo будет скорректирован в пределах
translateExtentЭта логика делает программные изменения идентичными пользовательским жестам.
В SVG-рендеринге координаты translateExtent обычно
привязываются к:
viewBox<g>Пример:
const svg = d3.select("svg");
const g = svg.append("g");
const zoom = d3.zoom()
.scaleExtent([1, 4])
.translateExtent([[0, 0], [1000, 600]])
.on("zoom", (event) => {
g.attr("transform", event.transform);
});
svg.call(zoom);
Если сцена выходит за границы 1000×600, D3 автоматически
ограничивает перемещение.
Если область ограничения меньше видимой области, поведение становится «зажатым»:
Если диапазон масштабирования минимален:
.scaleExtent([1, 1.1])
Если translateExtent не соответствует соотношению сторон
viewport:
Каждое zoom-событие формирует объект:
event.transform = {
x: number,
y: number,
k: number
};
При этом:
k всегда ограничен scaleExtent(x, y) всегда скорректированы по
translateExtentИменно этот объект является единственным источником истины для рендера сцены.
При необходимости вычисления координат курсора используется инверсия трансформации:
const point = d3.pointer(event);
const inverted = transform.invert(point);
Если активированы ограничения:
invert работает в рамках уже ограниченной
трансформацииВ современных версиях D3 zoom поддерживает плавные переходы. Ограничения применяются и во время анимации:
translateExtentИногда встроенных ограничений недостаточно, и поверх
scaleExtent и translateExtent добавляется
дополнительная логика:
.on("zoom", (event) => {
const t = event.transform;
const clampedX = Math.max(minX, Math.min(maxX, t.x));
const clampedY = Math.max(minY, Math.min(maxY, t.y));
g.attr("transform", `translate(${clampedX},${clampedY}) scale(${t.k})`);
});
Однако при использовании translateExtent такая
дополнительная обработка обычно избыточна, так как D3 уже выполняет
аналогичный clamp.
При использовании canvas вместо SVG:
scaleExtent и translateExtent остаются
логическими ограничителямиtransformk,
x, yЭто требует явного применения трансформации к контексту:
context.setTransform(k, 0, 0, k, x, y);
Именно здесь ограничения становятся критически важными для предотвращения выхода за пределы данных.
Система zoom в D3 формирует трёхуровневую модель:
scaleExtenttranslateExtentevent.transformЭти элементы работают совместно, формируя предсказуемое управление сценой, где любое действие пользователя или кода остаётся в рамках заданного пространства координат.