Упрощение визуализации на мобильных устройствах

Разработка интерактивных графиков для мобильных устройств требует отдельного подхода к проектированию интерфейсов и обработке данных. Ограниченная ширина экрана, сенсорное управление, производительность слабых устройств и особенности мобильных браузеров напрямую влияют на архитектуру визуализации.

D3.js предоставляет низкоуровневые инструменты для полной адаптации графиков под мобильные устройства:

  • динамическое изменение размеров SVG;
  • упрощение количества отображаемых данных;
  • управление поведением осей;
  • адаптация подписей;
  • обработка touch-событий;
  • переключение между режимами отображения.

Адаптивная структура SVG

Основой мобильной визуализации становится отказ от фиксированных размеров.

Неправильный подход

const svg = d3.select("#chart")
    .append("svg")
    .attr("width", 1200)
    .attr("height", 600);

Фиксированные размеры приводят к:

  • горизонтальному скроллу;
  • обрезке графика;
  • деформации элементов;
  • проблемам масштабирования.

Использование viewBox

Наиболее распространённый способ адаптации — применение viewBox.

const width = 1200;
const height = 600;

const svg = d3.select("#chart")
    .append("svg")
    .attr("viewBox", `0 0 ${width} ${height}`)
    .attr("preserveAspectRatio", "xMidYMid meet")
    .style("width", "100%")
    .style("height", "auto");

Преимущества

  • автоматическое масштабирование;
  • единая система координат;
  • отсутствие необходимости пересчитывать элементы;
  • совместимость с CSS.

Определение размеров экрана

D3.js часто комбинируется с обычным JavaScript для получения текущих размеров окна.

const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;

На основе этих значений создаются условия адаптации.

const isMobile = screenWidth < 768;

Изменение размеров графика под мобильные устройства

Десктопный режим

const width = 1000;
const height = 500;

Мобильный режим

const width = window.innerWidth - 20;
const height = 300;

Комбинированный вариант

const isMobile = window.innerWidth < 768;

const width = isMobile ? window.innerWidth - 20 : 1000;
const height = isMobile ? 300 : 500;

Сокращение количества данных

Одной из главных проблем мобильной визуализации становится перегрузка интерфейса.

Если график содержит:

  • тысячи точек;
  • длинные подписи;
  • десятки линий;
  • плотные столбцы,

то мобильный экран становится нечитаемым.


Агрегация данных

На мобильных устройствах часто отображают не полные данные, а агрегированные.

Исходные данные

[
    { date: "2025-01-01", value: 12 },
    { date: "2025-01-02", value: 14 },
    ...
]

Группировка по неделям

const weeklyData = d3.rollups(
    data,
    values => d3.mean(values, d => d.value),
    d => d3.timeWeek(new Date(d.date))
);

Эффект

  • уменьшается количество точек;
  • повышается читаемость;
  • снижается нагрузка на браузер.

Уменьшение количества подписей осей

Оси являются одной из главных причин визуального шума.

Проблема

const axis = d3.axisBottom(xScale);

D3 автоматически создаёт большое количество подписей.


Ограничение количества тиков

const axis = d3.axisBottom(xScale)
    .ticks(5);

Для мобильных экранов

const ticksCount = isMobile ? 3 : 10;

const axis = d3.axisBottom(xScale)
    .ticks(ticksCount);

Скрытие части подписей

Иногда количество меток невозможно уменьшить автоматически.

Скрытие каждой второй подписи

svg.selectAll(".x-axis text")
    .style("display", (d, i) => i % 2 ? "none" : "block");

Поворот подписей

Длинные подписи оси X плохо помещаются на мобильных экранах.

Решение

svg.selectAll(".x-axis text")
    .attr("transform", "rotate(-45)")
    .style("text-anchor", "end");

Сокращение текста подписей

Исходный вариант

"Сентябрь 2025"

Мобильный вариант

"Сен 25"

Форматирование через D3

const format = d3.timeFormat("%b %y");

Упрощение легенд

Большие легенды занимают значительную часть экрана.

Десктопный вариант

  • цвет;
  • описание;
  • дополнительные значения;
  • проценты;
  • индикаторы.

Мобильный вариант

  • только цвет;
  • краткое название.

Перенос легенды вниз

На узких экранах боковые легенды уменьшают рабочую область.

Решение

const legendY = isMobile ? height + 40 : 20;

Скрытие второстепенных элементов

На мобильных устройствах отображают только ключевые элементы.

Часто скрывают

  • сетку;
  • второстепенные линии;
  • вспомогательные оси;
  • декоративные элементы;
  • фоновые изображения.

Пример

if (isMobile) {
    svg.select(".grid").remove();
}

Упрощение анимаций

Сложные анимации сильно нагружают мобильные устройства.

Тяжёлая анимация

selection.transition()
    .duration(3000)
    .ease(d3.easeBounce);

Упрощённый вариант

selection.transition()
    .duration(300);

Полное отключение анимаций

Иногда мобильный режим полностью исключает transitions.

if (!isMobile) {
    bars.transition()
        .duration(1000)
        .attr("height", d => y(d.value));
}

Использование Canvas вместо SVG

SVG плохо масштабируется при большом количестве элементов.

Проблема SVG

При тысячах объектов:

  • падает FPS;
  • увеличивается память;
  • тормозит touch-взаимодействие.

Когда нужен Canvas

Canvas предпочтителен для:

  • scatter plot с тысячами точек;
  • heatmap;
  • real-time графиков;
  • потоковой визуализации.

Комбинирование D3 и Canvas

D3 может использоваться только для расчётов.

const x = d3.scaleLinear()
    .domain([0, 100])
    .range([0, width]);

А отрисовка выполняется через Canvas API.

context.fillRect(
    x(d.value),
    y(d.other),
    2,
    2
);

Оптимизация touch-событий

Мобильные устройства используют касания вместо мыши.


Замена mouseover

Десктопный вариант

selection.on("mouseover", handler);

Мобильный вариант

selection.on("touchstart", handler);

Универсальные pointer-события

Современный подход — использование pointer events.

selection.on("pointerenter", handler);

Преимущества

  • единый API;
  • поддержка мыши;
  • поддержка стилуса;
  • поддержка touch.

Увеличение области нажатия

Мелкие элементы трудно нажимать пальцем.

Проблема

.attr("r", 3)

Решение

.attr("r", isMobile ? 10 : 4)

Невидимые области взаимодействия

Иногда визуальный элемент остаётся маленьким, но зона нажатия увеличивается.

svg.append("circle")
    .attr("r", 20)
    .style("opacity", 0);

Упрощение tooltip

Tooltip на мобильных устройствах работает иначе.

Проблемы

  • отсутствует hover;
  • палец перекрывает данные;
  • всплывающие окна мешают интерфейсу.

Замена hover на tap

selection.on("click", (event, d) => {
    tooltip
        .style("display", "block")
        .html(d.value);
});

Фиксированные tooltip

На мобильных устройствах tooltip часто закрепляют сверху.

tooltip
    .style("top", "10px")
    .style("left", "10px");

Использование bottom sheet

Современные мобильные интерфейсы часто выводят информацию снизу экрана.

Преимущества

  • не перекрывает график;
  • удобно для пальцев;
  • поддерживает большие объёмы данных.

Уменьшение количества цветов

Слишком яркая палитра ухудшает восприятие на маленьких экранах.

Рекомендуется

  • 3–5 основных цветов;
  • высокий контраст;
  • отказ от мелких оттенков.

Увеличение толщины линий

Тонкие линии плохо видны на смартфонах.

Десктоп

.attr("stroke-width", 1)

Мобильные устройства

.attr("stroke-width", 3)

Увеличение шрифтов

Мелкий текст плохо читается при касании.

Адаптация

const fontSize = isMobile ? "14px" : "10px";

Уменьшение количества графиков

Многокомпонентные dashboards плохо работают на мобильных устройствах.

Вместо

  • 6 графиков одновременно.

Используют

  • переключение вкладок;
  • карусели;
  • последовательную навигацию.

Lazy rendering

Необязательно отрисовывать весь контент сразу.

Идея

График создаётся только после появления в области видимости.


Intersection Observer

const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            renderChart();
        }
    });
});

Динамическое изменение графика при resize

Мобильные устройства часто меняют ориентацию экрана.

Обработка resize

window.addEventListener("resize", () => {
    updateChart();
});

Полная перерисовка

Иногда проще удалить график и создать заново.

function updateChart() {
    d3.select("svg").remove();
    renderChart();
}

Использование медиазапросов CSS

D3.js обычно комбинируется с адаптивными стилями.

@media (max-width: 768px) {
    .chart-title {
        font-size: 16px;
    }
}

Вертикальные графики вместо горизонтальных

Широкие графики плохо помещаются на смартфонах.

Часто заменяют

  • горизонтальные bar chart;
  • wide timeline;
  • table-like layouts.

На

  • вертикальные блоки;
  • stacked chart;
  • card visualization.

Использование scrollable-контейнеров

Иногда упрощение невозможно без прокрутки.

Решение

.chart-wrapper {
    overflow-x: auto;
}

Минимизация DOM-элементов

Большое количество SVG-узлов замедляет мобильные браузеры.

Проблема

data.forEach(() => {
    svg.append("circle");
});

Тысячи SVG-элементов создают серьёзную нагрузку.


Оптимизация структуры

Вместо множества элементов

  • объединение path;
  • использование Canvas;
  • агрегация данных.

Использование path вместо множества линий

Неэффективно

svg.append("line");
svg.append("line");
svg.append("line");

Эффективно

svg.append("path")
    .attr("d", lineGenerator(data));

Упрощение географических карт

Карты особенно тяжело работают на мобильных устройствах.

Оптимизация включает

  • уменьшение детализации topojson;
  • скрытие второстепенных объектов;
  • отключение сложных теней;
  • отказ от лишней анимации.

Упрощение force-графов

Force simulation требует больших вычислений.

Настройки оптимизации

simulation.alphaDecay(0.1);

Ограничение количества узлов

const visibleNodes = data.slice(0, 100);

Предварительная обработка данных

На мобильных устройствах вычисления желательно выполнять заранее.

Плохо

const processed = heavyCalculation(data);

внутри каждого обновления графика.

Лучше

  • подготовить данные на сервере;
  • использовать кэш;
  • хранить агрегированные значения.

Использование debounce при resize

Без debounce resize вызывает множество перерисовок.

Решение

function debounce(fn, delay) {
    let timeout;

    return function() {
        clearTimeout(timeout);

        timeout = setTimeout(() => {
            fn();
        }, delay);
    };
}

Применение

window.addEventListener(
    "resize",
    debounce(updateChart, 200)
);

Progressive disclosure

На мобильных устройствах часто показывают только базовую информацию.

Сначала отображается

  • общая тенденция;
  • ключевые показатели;
  • краткая статистика.

Дополнительно открывается

  • детализация;
  • tooltip;
  • drill-down;
  • дополнительные серии.

Упрощение взаимодействия zoom

Сложный zoom неудобен на мобильных устройствах.

Часто отключают

if (!isMobile) {
    svg.call(zoom);
}

Ограниченный zoom

Иногда разрешают только масштабирование по одной оси.

zoom.scaleExtent([1, 3]);

Тестирование производительности

Даже корректно работающий график может тормозить на слабых смартфонах.

Проверяют

  • FPS;
  • объём памяти;
  • скорость touch-событий;
  • время рендера;
  • нагрузку CPU.

Наиболее распространённые мобильные проблемы

Перегруженность интерфейса

Причина:

  • слишком много данных;
  • избыточные подписи;
  • сложная легенда.

Медленная отрисовка

Причина:

  • тысячи SVG-элементов;
  • тяжёлые transitions;
  • постоянные пересчёты.

Проблемы touch-взаимодействия

Причина:

  • маленькие элементы;
  • hover-логика;
  • отсутствие pointer events.

Нечитаемые подписи

Причина:

  • мелкий шрифт;
  • длинные названия;
  • отсутствие адаптации осей.

Архитектура мобильной визуализации

Современный адаптивный график в D3.js обычно включает:

  1. Responsive SVG.
  2. Проверку размеров экрана.
  3. Отдельные mobile-настройки.
  4. Агрегацию данных.
  5. Упрощённые оси.
  6. Минимальное количество DOM-узлов.
  7. Touch-friendly взаимодействие.
  8. Lazy rendering.
  9. Canvas для тяжёлых сцен.
  10. Оптимизированные transitions.