Схемы и диаграммы связей

Библиотека Snap.svg предоставляет удобный инструментарий для создания интерактивных SVG-графиков, включая схемы связей, блок-схемы, графы и диаграммы. Основной подход заключается в комбинировании базовых примитивов (прямоугольников, кругов, линий) и управлении их положением, стилями и связями через JavaScript.

Схема обычно состоит из двух типов элементов:

  • узлы (nodes) — визуальные блоки, представляющие сущности;
  • связи (edges) — линии или кривые, соединяющие узлы.

Создание узлов

Узлы чаще всего реализуются через группы (g), объединяющие фигуру и текст:

var s = Snap("#svg");

function createNode(x, y, width, height, label) {
    var rect = s.rect(x, y, width, height, 6, 6)
        .attr({
            fill: "#f5f5f5",
            stroke: "#333",
            strokeWidth: 2
        });

    var text = s.text(x + width / 2, y + height / 2, label)
        .attr({
            textAnchor: "middle",
            alignmentBaseline: "middle",
            fontSize: 14
        });

    return s.group(rect, text);
}

var node1 = createNode(50, 50, 120, 50, "Узел 1");
var node2 = createNode(300, 200, 120, 50, "Узел 2");

Группировка позволяет перемещать узел как единое целое и применять к нему события.

Соединение узлов линиями

Простейшее соединение реализуется через линию:

function connectNodes(x1, y1, x2, y2) {
    return s.line(x1, y1, x2, y2)
        .attr({
            stroke: "#000",
            strokeWidth: 2
        });
}

var link = connectNodes(170, 75, 300, 225);

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

Динамические точки привязки

Для корректного соединения узлов необходимо учитывать их размеры и положение. Пример функции вычисления центра:

function getCenter(node) {
    var bbox = node.getBBox();
    return {
        x: bbox.cx,
        y: bbox.cy
    };
}

function connect(nodeA, nodeB) {
    var a = getCenter(nodeA);
    var b = getCenter(nodeB);

    return s.line(a.x, a.y, b.x, b.y)
        .attr({
            stroke: "#555",
            strokeWidth: 2
        });
}

var link = connect(node1, node2);

Использование кривых для связей

Для более сложных диаграмм (например, графов или UML) применяются кривые Безье:

function curvedConnection(nodeA, nodeB) {
    var a = getCenter(nodeA);
    var b = getCenter(nodeB);

    var dx = Math.abs(b.x - a.x) / 2;

    var path = `M${a.x},${a.y} C${a.x + dx},${a.y} ${b.x - dx},${b.y} ${b.x},${b.y}`;

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

var curvedLink = curvedConnection(node1, node2);

Кривые делают схему более читаемой при большом количестве пересечений.

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

Связи часто требуют указания направления. Для этого используются маркеры:

var arrow = s.polygon("0,0 10,5 0,10")
    .attr({ fill: "#000" });

var marker = arrow.marker(0, 0, 10, 10, 10, 5);

var line = s.line(100, 100, 300, 200)
    .attr({
        stroke: "#000",
        strokeWidth: 2,
        markerEnd: marker
    });

Маркер автоматически размещается на конце линии.

Перетаскивание узлов

Интерактивность — ключевая особенность схем. Snap.svg поддерживает drag-and-drop:

function makeDraggable(node) {
    var startX, startY;

    function start() {
        var bbox = node.getBBox();
        startX = bbox.x;
        startY = bbox.y;
    }

    function move(dx, dy) {
        node.transform(`translate(${startX + dx}, ${startY + dy})`);
    }

    node.drag(move, start);
}

makeDraggable(node1);
makeDraggable(node2);

Обновление связей при перемещении

Связи должны обновляться при перемещении узлов. Для этого связь хранит ссылки на узлы:

function dynamicConnection(nodeA, nodeB) {
    var path = s.path().attr({
        fill: "none",
        stroke: "#000",
        strokeWidth: 2
    });

    function update() {
        var a = getCenter(nodeA);
        var b = getCenter(nodeB);

        path.attr({
            d: `M${a.x},${a.y} L${b.x},${b.y}`
        });
    }

    nodeA.drag(update, update);
    nodeB.drag(update, update);

    update();

    return path;
}

dynamicConnection(node1, node2);

Такой подход обеспечивает постоянную синхронизацию.

Группировка и слои

При сложных схемах удобно разделять элементы:

  • слой узлов
  • слой связей
var linksLayer = s.group();
var nodesLayer = s.group();

var node = nodesLayer.add(createNode(100, 100, 120, 50, "A"));
var link = linksLayer.add(connect(node1, node2));

Это упрощает управление порядком отображения.

Стилизация и визуальные состояния

Схемы часто требуют выделения элементов:

node1.hover(
    function () {
        this.select("rect").attr({ fill: "#d0e6ff" });
    },
    function () {
        this.select("rect").attr({ fill: "#f5f5f5" });
    }
);

Также можно изменять стиль связей при наведении или клике.

Использование данных для генерации схем

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

var data = [
    { id: "A", x: 50, y: 50 },
    { id: "B", x: 250, y: 150 }
];

var links = [
    { from: "A", to: "B" }
];

var nodesMap = {};

data.forEach(function(item) {
    nodesMap[item.id] = createNode(item.x, item.y, 120, 50, item.id);
});

links.forEach(function(link) {
    connect(nodesMap[link.from], nodesMap[link.to]);
});

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

Автоматическое расположение

При большом количестве узлов ручное позиционирование неэффективно. Используются алгоритмы:

  • сетка (grid)
  • дерево (tree layout)
  • силовая модель (force-directed)

Пример простой сетки:

function layoutGrid(nodes, cols, spacingX, spacingY) {
    nodes.forEach(function(node, i) {
        var x = (i % cols) * spacingX;
        var y = Math.floor(i / cols) * spacingY;

        node.transform(`translate(${x}, ${y})`);
    });
}

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

При работе с большим количеством элементов важно учитывать:

  • минимизацию перерисовок;
  • использование групп;
  • обновление только изменённых элементов;
  • отказ от лишних анимаций.

Расширение функциональности

На базе Snap.svg можно реализовать:

  • редакторы диаграмм (drag, resize, connect);
  • визуализацию графов;
  • UML-диаграммы;
  • сетевые топологии.

Добавление пользовательских обработчиков и логики делает библиотеку гибким инструментом для построения сложных интерактивных систем.