Библиотека Snap.svg предоставляет удобный инструментарий для создания интерактивных SVG-графиков, включая схемы связей, блок-схемы, графы и диаграммы. Основной подход заключается в комбинировании базовых примитивов (прямоугольников, кругов, линий) и управлении их положением, стилями и связями через JavaScript.
Схема обычно состоит из двух типов элементов:
Узлы чаще всего реализуются через группы (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]);
});
Такой подход позволяет легко масштабировать систему.
При большом количестве узлов ручное позиционирование неэффективно. Используются алгоритмы:
Пример простой сетки:
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 можно реализовать:
Добавление пользовательских обработчиков и логики делает библиотеку гибким инструментом для построения сложных интерактивных систем.