Метод .data() в D3.js является ключевым механизмом
связывания данных с DOM-элементами. Он формирует основу декларативного
подхода библиотеки, при котором структура документа определяется не
вручную через императивные операции создания и удаления узлов, а через
соответствие между массивом данных и набором элементов в выборке.
В основе работы .data() лежит сопоставление элементов
массива с DOM-узлами выборки. Каждое значение из массива связывается с
конкретным элементом, формируя внутреннюю структуру данных внутри DOM
через скрытое поле __data__.
const data = [10, 20, 30];
d3.select("body")
.selectAll("div")
.data(data);
На этом этапе D3 не обязательно создаёт или удаляет элементы. Метод лишь устанавливает соответствие между данными и уже существующими элементами выборки. Если элементов меньше, чем данных, часть значений остаётся не привязанной и попадает в специальную подсекцию enter.
После вызова .data() выборка разделяется на три
логические группы:
Эта модель позволяет декларативно описывать изменения DOM в зависимости от состояния данных.
const selection = d3.select("body")
.selectAll("p")
.data([1, 2, 3, 4]);
Если в DOM было меньше <p>, чем элементов в
массиве, часть значений попадёт в enter.
Для работы с новыми данными используется метод .enter(),
который возвращает виртуальные элементы, не привязанные к DOM, но
представляющие новые данные.
const data = [1, 2, 3, 4];
const selection = d3.select("body")
.selectAll("p")
.data(data);
selection.enter()
.append("p")
.text(d => d);
В этом примере для каждого отсутствующего элемента создаётся новый
<p>, после чего данные отображаются через функцию
доступа d => d.
Часть выборки, относящаяся к update, используется для изменения уже существующих DOM-узлов. Это позволяет избегать пересоздания элементов при изменении данных.
const data = [5, 10, 15];
d3.selectAll("p")
.data(data)
.text(d => d);
Здесь текст существующих элементов обновляется в соответствии с новым массивом.
Когда данных становится меньше, чем элементов в DOM, часть узлов попадает в exit-секцию. Их необходимо явно удалить.
const data = [1, 2];
const selection = d3.selectAll("p")
.data(data);
selection.exit()
.remove();
Удаление через .remove() является обязательным шагом,
иначе DOM сохранит устаревшие элементы.
Каждый элемент, связанный через .data(), получает доступ
к своему значению через функцию-обработчик. Сигнатура функции
доступа:
function(d, i) {}
где:
d — текущий элемент данныхi — индекс элемента в массивеd3.selectAll("li")
.data([100, 200, 300])
.text((d, i) => `Index ${i}: ${d}`);
Такой подход позволяет учитывать как значение данных, так и позицию в массиве.
По умолчанию привязка данных осуществляется по индексу. Это может приводить к нежелательным пересозданиям элементов при изменении порядка массива. Для решения используется функция ключа.
const data = [
{ id: "a", value: 10 },
{ id: "b", value: 20 }
];
d3.selectAll("div")
.data(data, d => d.id);
Функция d => d.id определяет стабильную идентификацию
элементов. Теперь D3 сопоставляет данные не по позиции, а по уникальному
ключу, что позволяет корректно обрабатывать перестановки, вставки и
удаления.
Рассмотрим изменение структуры данных:
let data = [
{ id: 1, value: 10 },
{ id: 2, value: 20 }
];
const selection = d3.selectAll("div")
.data(data, d => d.id);
При обновлении:
data = [
{ id: 2, value: 25 },
{ id: 3, value: 30 }
];
selection.data(data, d => d.id);
id: 2 обновляетсяid: 1 попадает в exitid: 3 попадает в enterТакое поведение позволяет минимизировать DOM-операции и сохранять визуальную непрерывность интерфейса.
D3 внутренне хранит привязанные данные в свойстве
__data__ каждого DOM-узла.
const node = document.querySelector("div");
console.log(node.__data__);
Хотя прямое использование этого свойства не является частью публичного API, оно демонстрирует механизм хранения состояния.
Часто требуется единый поток обработки для новых и существующих
элементов. Для этого используется метод .merge().
const data = [1, 2, 3];
const update = d3.selectAll("div")
.data(data);
update.enter()
.append("div")
.merge(update)
.text(d => d);
Такой подход объединяет enter и update в одну цепочку, позволяя применять одинаковые стили и поведение.
Метод .data() работает не только с примитивами, но и с
объектами любой сложности.
const data = [
{ name: "alpha", value: 10 },
{ name: "beta", value: 20 }
];
d3.selectAll("section")
.data(data)
.text(d => `${d.name}: ${d.value}`);
Структуры данных могут включать вложенные объекты, массивы и
вычисляемые поля, что делает .data() универсальным
механизмом отображения состояния приложения.
Повторный вызов .data() на той же выборке пересчитывает
соответствие:
const selection = d3.selectAll("p");
selection.data([1, 2, 3]);
selection.data([4, 5]);
Каждая операция полностью перестраивает внутренние связи между DOM и массивом, включая пересчёт enter, update и exit.
Метод .data() является центральным звеном между данными
и визуальным представлением. Он обеспечивает:
Именно через него реализуется принцип, при котором визуальная структура рассматривается как функция данных, а не как набор вручную управляемых элементов.