При разработке визуализаций на базе D3.js важна не только корректная отрисовка графиков, но и их способность адаптироваться к различным размерам экранов. Современные интерфейсы должны одинаково хорошо работать на широкоформатных мониторах, планшетах и мобильных устройствах. Для решения этой задачи используются медиазапросы CSS, механизмы адаптивной верстки и динамические возможности D3.js.
D3.js напрямую не содержит встроенной системы медиазапросов, поскольку библиотека отвечает за работу с данными и DOM-структурой. Однако D3 прекрасно взаимодействует с CSS, JavaScript API браузера и SVG, что позволяет строить полностью адаптивные визуализации.
Медиазапросы (media queries) позволяют применять разные
CSS-правила в зависимости от характеристик устройства:
Пример классического медиазапроса:
@media screen and (max-width: 768px) {
.chart-title {
font-size: 14px;
}
}
В контексте D3.js медиазапросы используются для:
SVG обладает важным преимуществом — векторной природой. Это означает, что графика масштабируется без потери качества.
Базовый адаптивный SVG:
const svg = d3.select("#chart")
.append("svg")
.attr("viewBox", "0 0 800 400")
.attr("preserveAspectRatio", "xMidYMid meet")
.style("width", "100%")
.style("height", "auto");
viewBoxОпределяет внутреннюю систему координат SVG:
viewBox="0 0 800 400"
Структура:
min-x min-y width height
preserveAspectRatioУправляет поведением масштабирования:
.attr("preserveAspectRatio", "xMidYMid meet")
Наиболее популярные варианты:
| Значение | Описание |
|---|---|
meet |
Сохраняет пропорции |
slice |
Заполняет область с обрезкой |
none |
Игнорирует пропорции |
Часто требуется перестраивать график при изменении размеров окна браузера.
resizewindow.addEventListener("resize", resizeChart);
function resizeChart() {
const width = window.innerWidth;
svg.attr("width", width);
}
Однако простого изменения ширины обычно недостаточно. Необходимо:
Наиболее надежный подход — полная перерисовка визуализации.
function renderChart() {
d3.select("#chart")
.selectAll("*")
.remove();
const width = window.innerWidth * 0.8;
const height = 400;
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
// построение графика
}
renderChart();
window.addEventListener("resize", renderChart);
matchMediaAPI window.matchMedia() позволяет работать с
медиазапросами непосредственно из JavaScript.
Пример:
const mobileQuery = window.matchMedia("(max-width: 768px)");
if (mobileQuery.matches) {
console.log("Мобильное устройство");
}
matchMedia с D3.jsfunction render() {
const isMobile = window
.matchMedia("(max-width: 768px)")
.matches;
const width = isMobile ? 320 : 900;
const height = isMobile ? 250 : 500;
d3.select("#chart")
.selectAll("*")
.remove();
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append("text")
.attr("x", 20)
.attr("y", 40)
.text(isMobile ? "Mobile" : "Desktop");
}
render();
window.addEventListener("resize", render);
Размеры графиков обычно рассчитываются динамически.
const container = d3.select("#chart").node();
const width = container.clientWidth;
const height = container.clientHeight;
ResizeObserverСовременный способ отслеживания изменений размеров контейнера.
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const width = entry.contentRect.width;
console.log(width);
renderChart();
}
});
observer.observe(document.getElementById("chart"));
ResizeObserverwindow.resize;На мобильных устройствах длинные подписи осей создают серьезные проблемы.
const xAxis = d3.axisBottom(xScale)
.ticks(5);
Для десктопа:
.ticks(12)
Для мобильных:
.ticks(4)
svg.selectAll(".x-axis text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");
svg.selectAll(".x-axis text")
.style("display", (d, i) => {
return i % 2 === 0 ? "block" : "none";
});
Размеры текста должны изменяться вместе с экраном.
.chart-label {
font-size: 16px;
}
@media screen and (max-width: 768px) {
.chart-label {
font-size: 11px;
}
}
const fontSize = window.innerWidth < 768
? "10px"
: "16px";
svg.selectAll("text")
.style("font-size", fontSize);
На мобильных устройствах столбцы часто становятся слишком узкими.
Вместо вертикальной:
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", d => yScale(d.name))
.attr("width", d => xScale(d.value))
.attr("height", yScale.bandwidth());
Такой подход обеспечивает:
const radius = Math.min(width, height) / 2;
На десктопе:
[Диаграмма] [Легенда]
На мобильных:
[Диаграмма]
[Легенда]
При большом количестве точек визуализация становится нечитаемой.
Сокращение числа отображаемых элементов:
const reducedData = data.filter((d, i) => i % 5 === 0);
Толщина линий может изменяться:
const strokeWidth = window.innerWidth < 768
? 1
: 3;
path.attr("stroke-width", strokeWidth);
Мобильные устройства используют сенсорное управление.
Плохой вариант:
.attr("r", 3)
Лучший вариант:
.attr("r", 10)
.attr("opacity", 0)
Невидимая область облегчает касание элементов.
Событие mouseover не работает на большинстве сенсорных
устройств.
Вместо него используются:
.on("click", handler)
или:
.on("touchstart", handler)
Один из самых популярных шаблонов:
function responsivefy(svg) {
const container = d3.select(svg.node().parentNode);
const width = parseInt(svg.style("width"));
const height = parseInt(svg.style("height"));
const aspect = width / height;
svg.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMinYMid")
.call(resize);
d3.select(window)
.on("resize." + container.attr("id"), resize);
function resize() {
const targetWidth =
parseInt(container.style("width"));
svg.attr("width", targetWidth);
svg.attr("height",
Math.round(targetWidth / aspect));
}
}
Использование:
const svg = d3.select("#chart")
.append("svg")
.attr("width", 800)
.attr("height", 400);
responsivefy(svg);
D3 отлично работает внутри flex-контейнеров.
.dashboard {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
Каждый график:
.chart {
flex: 1 1 400px;
}
.dashboard {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(300px, 1fr));
}
Такой подход позволяет автоматически перестраивать сетку.
Tooltip должен корректно отображаться на маленьких экранах.
.tooltip {
max-width: 200px;
}
tooltip
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY + 10}px`);
Для мобильных устройств важно избегать выхода за пределы экрана.
Частая перерисовка может вызывать проблемы.
function debounce(fn, delay) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn();
}, delay);
};
}
Использование:
window.addEventListener(
"resize",
debounce(renderChart, 200)
);
SVG автоматически выглядит четко на Retina-дисплеях. Однако при
использовании Canvas требуется учитывать
devicePixelRatio.
Наиболее эффективная стратегия:
function drawChart() {
d3.select("#chart")
.selectAll("*")
.remove();
const container =
document.getElementById("chart");
const width = container.clientWidth;
const height = width < 600
? 300
: 500;
const svg = d3.select("#chart")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.style("width", "100%")
.style("height", "auto");
const data = [10, 20, 30, 40];
const xScale = d3.scaleBand()
.domain(data.map((d, i) => i))
.range([0, width])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, 40])
.range([height, 0]);
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("y", d => yScale(d))
.attr("width", xScale.bandwidth())
.attr("height",
d => height - yScale(d));
}
drawChart();
window.addEventListener(
"resize",
debounce(drawChart, 150)
);
Плохо:
.attr("width", 800)
.attr("height", 600)
Лучше:
.style("width", "100%")
.attr("viewBox", "0 0 800 600")
График может быть идеальным на десктопе и полностью нечитаемым на смартфоне.
Большое количество SVG-элементов резко снижает производительность мобильных браузеров.
При изменении размеров необходимо обновлять:
Типичная структура:
Container
└── SVG
├── Scales
├── Axes
├── Shapes
├── Labels
└── Interactions
При изменении размера:
Эффективная стратегия адаптивной визуализации — проектирование сначала под мобильные устройства.
Пример:
.chart-title {
font-size: 12px;
}
@media screen and (min-width: 768px) {
.chart-title {
font-size: 20px;
}
}
Преимущества:
Responsive-дизайн тесно связан с accessibility.
Важно учитывать:
В современных приложениях D3.js обычно используется совместно с:
Container Queries особенно полезны, поскольку позволяют адаптировать график не под окно браузера, а под размер конкретного контейнера.
Пример:
@container (max-width: 500px) {
.chart-label {
display: none;
}
}
Это особенно эффективно для dashboard-интерфейсов и модульных систем визуализации данных.