При работе с масштабированием в D3.js пользователь может изменять положение и масштаб области просмотра: приближать график, перемещать диаграмму, исследовать отдельные участки визуализации. После серии взаимодействий часто возникает необходимость вернуть представление к исходному состоянию.
Сброс зума — это программное восстановление начального масштаба и
позиции через вызов трансформации d3.zoomIdentity.
На практике такая возможность используется:
D3 хранит текущее состояние масштабирования в виде объекта трансформации:
{
x: 0,
y: 0,
k: 1
}
Где:
x — смещение по оси X;y — смещение по оси Y;k — коэффициент масштаба.Исходное состояние задаётся через:
d3.zoomIdentity
Эквивалентно:
{
x: 0,
y: 0,
k: 1
}
Именно к этому состоянию обычно выполняется сброс.
Простейшая схема масштабирования:
<svg width="800" height="400"></svg>
<button id="reset">Сбросить зум</button>
const svg = d3.select("svg");
const container = svg.append("g");
container.append("circle")
.attr("cx", 200)
.attr("cy", 150)
.attr("r", 80)
.attr("fill", "steelblue");
Создание поведения zoom:
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
Подключение zoom к SVG:
svg.call(zoom);
Функция обновления трансформации:
function zoomed(event) {
container.attr("transform", event.transform);
}
После этого SVG начинает поддерживать:
Сброс выполняется через метод:
zoom.transform
Полный пример:
d3.select("#reset").on("click", () => {
svg.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity
);
});
Метод:
zoom.transform
принудительно устанавливает новую трансформацию для элемента.
Аргументы:
selection.call(
zoom.transform,
transform
);
Где:
selection — SVG или контейнер;transform — объект трансформации.В роли трансформации может использоваться:
d3.zoomIdentity
или произвольное состояние:
d3.zoomIdentity.translate(100, 50).scale(2)
Без transition:
svg.call(
zoom.transform,
d3.zoomIdentity
);
Сброс произойдёт мгновенно.
С transition:
svg.transition()
.duration(500)
.call(
zoom.transform,
d3.zoomIdentity
);
D3 интерполирует:
Это создаёт плавный возврат камеры.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
font-family: Arial;
}
button {
margin-bottom: 10px;
padding: 8px 16px;
}
svg {
border: 1px solid #999;
}
</style>
</head>
<body>
<button id="reset">
Сбросить зум
</button>
<svg width="800" height="400"></svg>
<script>
const svg = d3.select("svg");
const container = svg.append("g");
for (let i = 0; i < 20; i++) {
container.append("circle")
.attr("cx", Math.random() * 700)
.attr("cy", Math.random() * 300)
.attr("r", 20)
.attr("fill", "steelblue");
}
const zoom = d3.zoom()
.scaleExtent([0.5, 10])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed(event) {
container.attr(
"transform",
event.transform
);
}
d3.select("#reset")
.on("click", () => {
svg.transition()
.duration(700)
.call(
zoom.transform,
d3.zoomIdentity
);
});
</script>
</body>
</html>
Иногда необходимо возвращать пользователя не к исходному состоянию, а к заранее определённой области.
Пример:
const defaultTransform =
d3.zoomIdentity
.translate(200, 100)
.scale(2);
Применение:
svg.transition()
.duration(600)
.call(
zoom.transform,
defaultTransform
);
Такой подход используется:
Частая задача — вернуть масштаб и одновременно центрировать объект.
Пример:
const transform =
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1.5);
Важно понимать:
translate()
выполняется до масштабирования.
Порядок операций влияет на итоговые координаты.
D3 позволяет получить текущее состояние zoom:
const transform =
d3.zoomTransform(svg.node());
Пример:
console.log(transform.k);
Получение:
console.log(transform.x);
console.log(transform.y);
Это полезно при:
Иногда требуется сбросить только масштаб, но сохранить позицию.
Пример:
const current =
d3.zoomTransform(svg.node());
const transform =
d3.zoomIdentity
.translate(current.x, current.y)
.scale(1);
Применение:
svg.transition()
.call(
zoom.transform,
transform
);
Обратная задача — вернуть положение, сохранив масштаб.
const current =
d3.zoomTransform(svg.node());
const transform =
d3.zoomIdentity
.scale(current.k);
Если интерфейс содержит несколько визуализаций:
const charts = d3.selectAll("svg");
Сброс:
charts.transition()
.duration(500)
.call(
zoom.transform,
d3.zoomIdentity
);
Иногда после программного изменения трансформации возникают проблемы рассинхронизации.
Неправильный подход:
container.attr(
"transform",
"translate(0,0) scale(1)"
);
Так делать нельзя.
Причина:
Правильный способ:
svg.call(
zoom.transform,
d3.zoomIdentity
);
Даже после сброса продолжают действовать ограничения:
.scaleExtent([1, 5])
Минимальный и максимальный zoom:
const zoom = d3.zoom()
.scaleExtent([1, 5]);
При попытке установить:
.scale(10)
D3 автоматически ограничит значение.
Иногда сброс привязывают к двойному клику:
svg.on("dblclick", () => {
svg.transition()
.duration(400)
.call(
zoom.transform,
d3.zoomIdentity
);
});
По умолчанию D3 масштабирует SVG по двойному клику.
Отключение:
svg.call(zoom)
.on("dblclick.zoom", null);
После этого двойной клик можно использовать для собственного сброса.
При обновлении визуализации масштаб часто нужно обнулять:
function update(data) {
render(data);
svg.call(
zoom.transform,
d3.zoomIdentity
);
}
Это особенно важно:
Ошибка:
container.attr("transform", null);
Проблема:
Ошибка:
container.call(
zoom.transform,
d3.zoomIdentity
);
Корректно:
svg.call(
zoom.transform,
d3.zoomIdentity
);
Zoom должен вызываться на том же элементе, где был зарегистрирован.
Без анимации пользователь может потерять ориентацию.
Предпочтительно:
.transition()
.duration(500)
В крупных приложениях полезно хранить zoom отдельно:
const state = {
zoom: d3.zoomIdentity
};
Обновление:
function zoomed(event) {
state.zoom = event.transform;
container.attr(
"transform",
event.transform
);
}
Сброс:
state.zoom = d3.zoomIdentity;
svg.call(
zoom.transform,
state.zoom
);
Такой подход облегчает:
В React кнопка сброса обычно вызывает:
svg.transition()
.duration(500)
.call(
zoom.transform,
d3.zoomIdentity
);
Важно избегать:
Часто zoom хранится в useRef():
const zoomRef = useRef();
Сброс zoom практически не нагружает браузер, поскольку изменяется только:
transform=""
Основная нагрузка возникает:
Для Canvas-графики сброс выполняется ещё быстрее.
В Canvas D3 управляет только состоянием трансформации:
context.setTransform(
1,
0,
0,
1,
0,
0
);
Но zoom-состояние всё равно рекомендуется обновлять через:
zoom.transform
Часто кнопка сброса заменяется более сложной логикой:
fitToScreen()
Алгоритм:
Пример:
const transform =
d3.zoomIdentity
.translate(tx, ty)
.scale(scale);
Наиболее распространённый вариант:
const zoom = d3.zoom()
.scaleExtent([0.5, 8])
.on("zoom", ({transform}) => {
container.attr(
"transform",
transform
);
});
svg.call(zoom);
function resetZoom() {
svg.transition()
.duration(600)
.call(
zoom.transform,
d3.zoomIdentity
);
}
Такой шаблон используется в большинстве интерактивных визуализаций на D3.js.