Графовые структуры в D3.js обычно строятся вокруг двух базовых сущностей: узлов и рёбер. Такая модель данных отражает математическое представление графа, где множество вершин соединяется множеством связей, каждая из которых может нести дополнительную семантику. В контексте визуализации это преобразуется в систему объектов, пригодных для симуляции, раскладки и интерактивного отображения.
Наиболее распространённое представление графа в D3.js включает два массива:
Каждый элемент массива nodes представляет отдельную сущность графа:
const nodes = [
{ id: "A", group: 1 },
{ id: "B", group: 1 },
{ id: "C", group: 2 }
];
Каждый объект может содержать произвольные поля. На практике часто используются:
id — уникальный идентификатор узлаgroup — категория для раскраски или кластеризацииvalue — вес или размер узлаРёбра задаются отдельным массивом:
const links = [
{ source: "A", target: "B", weight: 1 },
{ source: "B", target: "C", weight: 2 }
];
Каждое ребро описывает направленную или ненаправленную связь между двумя узлами.
В D3.js поля source и target могут хранить
либо строки-идентификаторы, либо ссылки на объекты узлов. При
использовании симуляции d3.forceSimulation библиотека
автоматически преобразует идентификаторы в ссылки на соответствующие
объекты nodes.
После обработки структура рёбер фактически меняется:
{
source: { id: "A", group: 1, x: 120, y: 80 },
target: { id: "B", group: 1, x: 200, y: 150 },
weight: 1
}
Это преобразование является ключевым механизмом, обеспечивающим динамическое поведение графа.
Перед передачей данных в D3 часто выполняется нормализация:
idПример нормализации:
const nodeMap = new Map(nodes.map(n => [n.id, n]));
const normalizedLinks = links
.filter(l => nodeMap.has(l.source) && nodeMap.has(l.target))
.map(l => ({
source: nodeMap.get(l.source),
target: nodeMap.get(l.target),
weight: l.weight
}));
Такой подход обеспечивает согласованность структуры и снижает риск появления “висячих” связей.
В d3-force граф рассматривается как динамическая
система. Узлы получают дополнительные параметры во время расчёта:
x, y — текущие координатыvx, vy — скорость по осямindex — внутренний индекс симуляцииИзначально эти поля отсутствуют в данных и добавляются библиотекой автоматически.
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
Здесь граф перестаёт быть статической структурой и становится системой взаимодействующих сил.
Поле id выполняет критическую функцию: оно связывает
логическую модель данных с физической моделью симуляции.
Основные требования к идентификаторам:
При использовании обновляемых данных D3 опирается на ключи для сопоставления старых и новых узлов:
const node = svg.selectAll("circle")
.data(nodes, d => d.id);
Стабильность ключей обеспечивает корректную работу enter/update/exit механизма.
Графовые данные в D3 обычно отображаются через SVG или Canvas. Каждый узел становится визуальным примитивом:
circle для узловline или path для рёберСвязь между данными и DOM устанавливается декларативно:
const link = svg.selectAll("line")
.data(links)
.join("line");
const node = svg.selectAll("circle")
.data(nodes)
.join("circle");
DOM-элементы синхронизируются с массивами данных, а изменения структуры графа автоматически отражаются в визуализации.
Модель данных позволяет описывать оба типа графов.
Связь интерпретируется симметрично:
{ source: "A", target: "B" }
Направление имеет значение и может использоваться для стрелок или потоков:
{ source: "A", target: "B", directed: true }
Визуально направление часто отображается через маркеры SVG:
svg.append("defs")
.append("marker")
.attr("id", "arrow")
Дополнительные поля в links позволяют вводить количественные характеристики:
weight — сила связиdistance — желаемая длина ребраcapacity — пропускная способностьcost — вес в алгоритмах поиска путиЭти значения могут напрямую влиять на силу физических моделей:
d3.forceLink(links)
.distance(d => 100 / d.weight);
Таким образом структура данных становится входом для математической модели симуляции.
Хотя D3 работает с массивами, часто возникает необходимость построения структуры смежности.
Преобразование links в adjacency list:
const adjacency = new Map();
links.forEach(({ source, target }) => {
if (!adjacency.has(source)) adjacency.set(source, []);
adjacency.get(source).push(target);
});
Такая структура ускоряет алгоритмы поиска соседей, подсветки и анализа кластеров.
Хотя базовая модель ориентирована на произвольные графы, данные могут расширяться до иерархических структур:
const nodes = [
{ id: "root", parent: null },
{ id: "child1", parent: "root" }
];
Такие данные могут быть преобразованы в деревья для использования с
d3-hierarchy, но сохраняют графовую природу на уровне
хранения.
Модель данных в D3 предполагает изменяемость. Узлы и рёбра могут добавляться или удаляться без пересоздания всей структуры.
Добавление узла:
nodes.push({ id: "D" });
Добавление связи:
links.push({ source: "C", target: "D" });
После изменения данных симуляция может быть перезапущена или “разогнана” повторным вызовом:
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();
Модель графа в D3 не фиксирована и допускает произвольные расширения:
Это превращает графовую структуру в универсальный контейнер данных, пригодный для моделирования сетей различной природы: социальных, транспортных, информационных и вычислительных.