Работа с географическими точками в D3.js строится вокруг преобразования координат данных (широта и долгота) в экранные координаты SVG или Canvas. Основой служит связка географических проекций, масштабирования и визуальных примитивов, чаще всего окружностей.
Ключевой элемент — географическая проекция, преобразующая сферические координаты в плоские:
const projection = d3.geoMercator()
.scale(150)
.translate([width / 2, height / 2]);
Проекция определяет способ отображения карты и влияет на визуальную
искаженность объектов. geoMercator используется чаще всего
в веб-картах, так как сохраняет углы и подходит для общего анализа
распределений.
Любая точка на карте задаётся парой значений:
D3 предоставляет функцию преобразования:
const [x, y] = projection([longitude, latitude]);
Результат — координаты в пикселях, которые можно использовать для размещения SVG-элементов.
Пример структуры данных:
const data = [
{ name: "A", coordinates: [71.4304, 51.1282] },
{ name: "B", coordinates: [37.6173, 55.7558] }
];
Создание точек реализуется через привязку данных к элементам DOM:
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => projection(d.coordinates)[0])
.attr("cy", d => projection(d.coordinates)[1])
.attr("r", 4)
.attr("fill", "steelblue");
Каждый объект данных превращается в визуальный маркер. Такой подход обеспечивает декларативное связывание данных и визуализации.
Для отображения величины события или значения используется масштабирование радиуса окружностей.
D3 предоставляет шкалы:
const radiusScale = d3.scaleSqrt()
.domain([0, d3.max(data, d => d.value)])
.range([2, 30]);
Использование квадратного корня позволяет компенсировать визуальное восприятие площади.
Применение:
.attr("r", d => radiusScale(d.value))
Пузырьковая карта (bubble map) представляет точки с радиусом, пропорциональным значению метрики. Она применяется для отображения:
Структура данных:
[
{ coordinates: [71.4304, 51.1282], value: 120 },
{ coordinates: [55.7558, 37.6173], value: 300 }
]
Рендеринг:
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => projection(d.coordinates)[0])
.attr("cy", d => projection(d.coordinates)[1])
.attr("r", d => radiusScale(d.value))
.attr("fill", "orange")
.attr("opacity", 0.6);
Для отображения карты используется формат GeoJSON. Он описывает географические объекты в виде полигонов.
d3.json("world.geojson").then(world => {
svg.append("path")
.datum(world)
.attr("d", d3.geoPath().projection(projection))
.attr("fill", "#eee")
.attr("stroke", "#999");
});
geoPath преобразует географические данные в
SVG-path.
Корректная визуализация требует разделения слоёв:
Пример структуры:
const mapLayer = svg.append("g");
const pointsLayer = svg.append("g");
const overlayLayer = svg.append("g");
Такое разделение упрощает управление z-index и взаимодействием.
D3 позволяет добавлять поведение к элементам через события:
pointsLayer.selectAll("circle")
.data(data)
.enter()
.append("circle")
.on("mouseover", function(event, d) {
d3.select(this)
.attr("fill", "red")
.attr("r", radiusScale(d.value) * 1.2);
})
.on("mouseout", function(event, d) {
d3.select(this)
.attr("fill", "orange")
.attr("r", radiusScale(d.value));
});
Такая модель обеспечивает динамическую обратную связь без перерисовки всей сцены.
При большом количестве точек визуальная перегрузка решается кластеризацией.
Подходы:
Пример использования d3.quadtree:
const quadtree = d3.quadtree()
.x(d => projection(d.coordinates)[0])
.y(d => projection(d.coordinates)[1])
.addAll(data);
Quadtree позволяет эффективно искать соседние точки и группировать их.
SVG ограничен количеством DOM-элементов. При десятках тысяч точек используется Canvas:
const context = canvas.node().getContext("2d");
data.forEach(d => {
const [x, y] = projection(d.coordinates);
context.beginPath();
context.arc(x, y, radiusScale(d.value), 0, 2 * Math.PI);
context.fillStyle = "rgba(255, 165, 0, 0.6)";
context.fill();
});
Canvas повышает производительность за счёт отказа от DOM.
Переходы D3 позволяют плавно изменять визуальные параметры:
svg.selectAll("circle")
.data(data)
.join("circle")
.transition()
.duration(800)
.attr("r", d => radiusScale(d.value));
Анимация используется при обновлении данных или изменении масштаба карты.
При zoom-поведении необходимо пересчитывать координаты:
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", (event) => {
mapLayer.attr("transform", event.transform);
pointsLayer.attr("transform", event.transform);
});
svg.call(zoom);
Zoom изменяет трансформацию всей сцены, сохраняя географическую согласованность.
Основные методы оптимизации:
scaleSqrt вместо линейного масштаба
радиусовЦвета позволяют дополнительно кодировать данные:
const colorScale = d3.scaleSequential(d3.interpolateOrRd)
.domain([0, d3.max(data, d => d.value)]);
Применение:
.attr("fill", d => colorScale(d.value))
Цветовая шкала усиливает различимость значений при плотных распределениях.
В сложных случаях точки комбинируются с heatmap-подобным представлением:
Такой подход снижает визуальный шум и улучшает читаемость распределений данных на карте.