Географические данные в веб-визуализации обычно представлены в виде GeoJSON или TopoJSON-структур, где каждую геометрическую сущность необходимо преобразовать в экранные координаты. В D3.js за это отвечает механизм проекций (projection) и генератор пути (geoPath).
Базовая связка выглядит следующим образом:
Ключевая особенность: geoPath не хранит состояние
масштаба самостоятельно. Масштабирование реализуется через изменение
параметров проекции или через трансформации SVG-контейнера.
Минимальная конфигурация карты строится вокруг проекции:
const projection = d3.geoMercator()
.scale(150)
.translate([width / 2, height / 2]);
const path = d3.geoPath()
.projection(projection);
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", path);
На этом этапе карта статична: все координаты рассчитаны один раз
через projection.
Существует два основных способа реализовать масштабирование:
transform)Оба подхода применяются в зависимости от задачи.
Наиболее распространённый метод — использование d3.zoom,
который изменяет матрицу трансформации SVG-контейнера.
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed(event) {
g.attr("transform", event.transform);
}
Структура SVG:
<svg>
<g class="map-layer">
<!-- пути стран -->
</g>
</svg>
Все географические элементы помещаются в <g>,
который и трансформируется.
event.transform содержит:
k — коэффициент масштабаx, y — смещениеФактически применяется аффинное преобразование:
x' = kx + tx
y' = ky + ty
SVG выполняет это аппаратно, без пересчёта геометрии.
Несмотря на простоту, есть ограничения:
Альтернативный подход — изменение параметров проекции.
function zoomed(event) {
const { transform } = event;
projection
.scale(150 * transform.k)
.translate([
width / 2 + transform.x,
height / 2 + transform.y
]);
svg.selectAll("path")
.attr("d", path);
}
Здесь происходит полный пересчёт геометрии через
geoPath.
geoPath всегда использует актуальную проекцию:
const path = d3.geoPath().projection(projection);
При каждом изменении projection:
Преимущества:
Недостатки:
Преимущества:
Недостатки:
В сложных картах применяется комбинированная модель:
transform — для мгновенного откликаprojection update — для финальной стабилизацииfunction zoomed(event) {
g.attr("transform", event.transform);
projection.scale(baseScale * event.transform.k);
requestAnimationFrame(() => {
g.selectAll("path").attr("d", path);
});
}
Так достигается баланс между плавностью и точностью.
При масштабировании часто требуется удерживать центр под курсором.
D3.js предоставляет event.transform уже с учётом точки
фокуса:
const zoom = d3.zoom()
.on("zoom", (event) => {
g.attr("transform", event.transform);
});
Но при работе с projection важно корректировать translate:
projection.translate([
width / 2 + event.transform.x,
height / 2 + event.transform.y
]);
d3.zoom()
.scaleExtent([1, 10])
Где:
При работе с несколькими слоями (страны, города, дороги):
const layers = g.selectAll("g.layer")
.data(dataLayers)
.enter()
.append("g")
.attr("class", d => d.type);
Zoom применяется к контейнеру:
g.attr("transform", event.transform);
Тексты не всегда должны масштабироваться линейно:
labels.attr("transform", event.transform)
.style("font-size", `${10 / event.transform.k}px`);
Это предотвращает визуальное «разрастание» подписей.
При тысячах геометрий:
SVG начинает деградировать при большом количестве DOM-узлов.
Для ограничения области отображения:
svg.append("clipPath")
.attr("id", "map-clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.attr("clip-path", "url(#map-clip)");
Это предотвращает выход элементов за границы viewport при трансформациях.
При наличии событий click/hover:
.on("click", (event, d) => {
const [x, y] = path.centroid(d);
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity.translate(width/2 - x, height/2 - y).scale(4));
});
Используется программное управление zoom через
zoom.transform.
При увеличении масштаба можно переключать детализацию данных:
if (event.transform.k > 3) {
loadDetailedGeojson();
}
Это снижает нагрузку и улучшает UX.
Типичная структура приложения:
g для всех гео-объектовТакой подход обеспечивает: