Визуализация данных

Визуализация данных представляет собой процесс преобразования числовой или категориальной информации в графические элементы. В контексте Snap.svg это означает работу с примитивами SVG — линиями, кругами, прямоугольниками и путями — для построения графиков, диаграмм и интерактивных схем.

SVG (Scalable Vector Graphics) обеспечивает масштабируемость и точность отображения, а Snap.svg предоставляет удобный API для манипуляции этими элементами.

Ключевые преимущества использования Snap.svg:

  • декларативное создание графики
  • удобная работа с координатами и трансформациями
  • встроенная поддержка анимаций
  • возможность добавления интерактивности

Подготовка данных

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

const data = [
    { label: "A", value: 30 },
    { label: "B", value: 80 },
    { label: "C", value: 45 },
    { label: "D", value: 60 }
];

Основные этапы подготовки:

  • нормализация значений (например, приведение к диапазону)
  • вычисление максимума и минимума
  • сортировка (если требуется)
  • преобразование в координаты

Создание базовой области визуализации

Инициализация Snap.svg:

const s = Snap("#svg");

Создание рабочей области:

const width = 500;
const height = 300;

s.attr({
    viewBox: `0 0 ${width} ${height}`
});

Добавление отступов:

const padding = 40;

Построение столбчатой диаграммы

Вычисление масштабов

const maxValue = Math.max(...data.map(d => d.value));
const barWidth = (width - padding * 2) / data.length;

Отрисовка столбцов

data.forEach((d, i) => {
    const barHeight = (d.value / maxValue) * (height - padding * 2);

    const x = padding + i * barWidth;
    const y = height - padding - barHeight;

    s.rect(x, y, barWidth - 10, barHeight)
        .attr({
            fill: "#3498db"
        });
});

Подписи

data.forEach((d, i) => {
    const x = padding + i * barWidth + (barWidth / 2);

    s.text(x, height - 10, d.label)
        .attr({
            textAnchor: "middle"
        });
});

Линейный график

Генерация точек

const stepX = (width - padding * 2) / (data.length - 1);

const points = data.map((d, i) => {
    const x = padding + i * stepX;
    const y = height - padding - (d.value / maxValue) * (height - padding * 2);
    return { x, y };
});

Построение пути

let pathString = `M ${points[0].x} ${points[0].y}`;

for (let i = 1; i < points.length; i++) {
    pathString += ` L ${points[i].x} ${points[i].y}`;
}

s.path(pathString)
    .attr({
        fill: "none",
        stroke: "#e74c3c",
        strokeWidth: 2
    });

Точки на графике

points.forEach(p => {
    s.circle(p.x, p.y, 4)
        .attr({ fill: "#e74c3c" });
});

Круговая диаграмма

Сумма значений

const total = data.reduce((sum, d) => sum + d.value, 0);

Генерация сегментов

let startAngle = 0;

data.forEach(d => {
    const angle = (d.value / total) * 360;
    const endAngle = startAngle + angle;

    const x1 = 250 + 100 * Math.cos(Math.PI * startAngle / 180);
    const y1 = 150 + 100 * Math.sin(Math.PI * startAngle / 180);

    const x2 = 250 + 100 * Math.cos(Math.PI * endAngle / 180);
    const y2 = 150 + 100 * Math.sin(Math.PI * endAngle / 180);

    const largeArc = angle > 180 ? 1 : 0;

    const path = `
        M 250 150
        L ${x1} ${y1}
        A 100 100 0 ${largeArc} 1 ${x2} ${y2}
        Z
    `;

    s.path(path)
        .attr({
            fill: Snap.hsl(Math.random() * 360, 70, 50)
        });

    startAngle = endAngle;
});

Добавление осей координат

Ось X

s.line(padding, height - padding, width - padding, height - padding)
    .attr({ stroke: "#000" });

Ось Y

s.line(padding, padding, padding, height - padding)
    .attr({ stroke: "#000" });

Деления

const steps = 5;

for (let i = 0; i <= steps; i++) {
    const y = height - padding - i * (height - padding * 2) / steps;
    const value = (maxValue / steps) * i;

    s.text(5, y, Math.round(value));
    s.line(padding - 5, y, padding, y)
        .attr({ stroke: "#000" });
}

Анимация графиков

Snap.svg предоставляет метод .animate():

rect.animate(
    { height: barHeight, y: y },
    800,
    mina.easeinout
);

Пример для линии:

path.attr({ strokeDasharray: pathLength, strokeDashoffset: pathLength });

path.animate(
    { strokeDashoffset: 0 },
    1000
);

Интерактивность

Наведение мыши

element.hover(
    function () {
        this.attr({ fill: "#2ecc71" });
    },
    function () {
        this.attr({ fill: "#3498db" });
    }
);

Всплывающие подсказки

const tooltip = s.text(0, 0, "")
    .attr({ opacity: 0 });

element.mousemove(function (e) {
    tooltip.attr({
        x: e.offsetX + 10,
        y: e.offsetY - 10,
        text: d.value,
        opacity: 1
    });
});

Группировка элементов

const group = s.group();

group.add(rect);
group.add(text);

group.transform("translate(10, 0)");

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

  • совместное перемещение
  • управление слоями
  • единая анимация

Адаптивность

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

s.attr({
    viewBox: "0 0 500 300",
    width: "100%",
    height: "auto"
});

Масштабирование без потери качества — ключевое преимущество SVG.


Оптимизация производительности

  • минимизация количества DOM-элементов
  • использование групп (g)
  • кеширование вычислений
  • отказ от лишних анимаций

Расширенные техники

Градиенты

const gradient = s.gradient("L(0,0,1,1)#3498db-#9b59b6");

rect.attr({
    fill: gradient
});

Маски

const mask = s.circle(250, 150, 80);

element.attr({
    mask: mask
});

Клиппинг

const clip = s.rect(100, 100, 200, 200);

element.attr({
    clipPath: clip
});

Архитектура визуализации

Рекомендуемый подход:

  • слой данных (массивы, API)
  • слой преобразования (масштабирование, координаты)
  • слой отображения (Snap.svg)
  • слой взаимодействия (события)

Разделение ответственности упрощает поддержку и расширение визуализации.


Частые ошибки

  • неправильная нормализация данных
  • отсутствие учета padding
  • чрезмерное количество элементов
  • игнорирование масштабирования
  • ручное вычисление без абстракций

Практика композиции

Комбинирование различных типов графиков:

  • столбчатая + линия
  • круговая + легенда
  • тепловые карты

Snap.svg позволяет строить сложные визуализации через объединение простых примитивов.


Итоговая структура кода визуализации

function renderChart(data) {
    const s = Snap("#svg");

    const scales = calculateScales(data);
    drawAxes(s, scales);
    drawBars(s, data, scales);
    addLabels(s, data, scales);
    addInteractions(s);
}

Такой подход облегчает повторное использование и тестирование компонентов визуализации.