data() без ключевой функцииМетод привязки данных к DOM-элементам в D3.js основан на концепции data join — сопоставления массива данных с выборкой элементов:
selection.data(dataArray)
По умолчанию сопоставление выполняется по индексу:
0 получает данные
data[0]1 получает данные
data[1]Такой механизм прост, но имеет важное ограничение: он предполагает, что порядок и состав данных стабильны. При изменении массива (добавление, удаление, перестановка элементов) происходит пересопоставление по позициям, а не по смысловой идентичности объектов.
Это приводит к эффекту, который в D3 называется потерей object constancy — устойчивости связи между данными и визуальными элементами.
Ключевая функция (key function) позволяет задать правило сопоставления данных и DOM-элементов по уникальному идентификатору, а не по индексу:
selection.data(dataArray, keyFunction)
Где:
(d, i) => key
d — текущий элемент данныхi — индексКлючевая функция превращает data join из позиционного в идентификационный.
Это означает:
Рассмотрим список:
let data = [
{ id: 1, value: 10 },
{ id: 2, value: 20 },
{ id: 3, value: 30 }
];
При обновлении:
data = [
{ id: 2, value: 25 },
{ id: 3, value: 35 },
{ id: 4, value: 40 }
];
Без ключевой функции:
id: 2 оказывается на позиции 0, но связывается с первым
DOM-элементомТакое поведение критично для:
При использовании ключа D3 строит внутреннюю карту соответствий:
selection.data(data, d => d.id)
Алгоритм:
Для каждого DOM-элемента берётся его привязанный datum
Из него извлекается ключ
Для нового массива данных вычисляются ключи
Производится сопоставление:
const circles = svg.selectAll("circle")
.data(data, d => d.id);
circles.enter()
.append("circle")
.attr("r", 5);
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
circles.exit().remove();
Ключевая функция d => d.id обеспечивает стабильную
связь между объектом данных и элементом <circle>.
Ключ должен быть:
Каждое значение должно однозначно идентифицировать объект:
d => d.id
Значение не должно меняться при обновлениях:
❌ плохо:
d => d.value
✔ правильно:
d => d.uuid
Чаще всего используются:
Использование ключевой функции радикально влияет на три стадии data join.
Создаются элементы только для новых ключей:
.enter()
Пример: добавился новый id: 4 → создаётся новый
DOM-элемент.
Обновляются элементы с совпавшими ключами:
selection
.attr("x", d => d.x);
Важно: DOM-элемент сохраняется, меняется только его состояние.
Элементы, ключи которых отсутствуют в новых данных:
.exit()
.remove();
Без ключей анимации становятся нестабильными: элементы могут «перескакивать» между значениями.
С ключами обеспечивается:
Пример:
circles
.transition()
.duration(500)
.attr("cx", d => xScale(d.value));
Если ключ стабилен, D3 понимает, какой именно элемент должен анимироваться.
data = [
{ meta: { id: "a1" }, value: 10 },
{ meta: { id: "a2" }, value: 20 }
];
selection.data(data, d => d.meta.id);
Иногда одного поля недостаточно:
d => `${d.group}-${d.id}`
Пример:
Если ключ не уникален:
.data(data, (d, i) => i)
Это эквивалентно отсутствию ключа в динамических данных и приводит к:
d => Math.random()
Каждый рендер создаёт новую идентичность → D3 воспринимает все элементы как новые.
Ключевая функция вводит важную абстракцию:
DOM-элемент становится «контейнером состояния конкретного объекта данных»
Это означает:
При работе с потоковыми данными (например, WebSocket):
function update(data) {
const circles = svg.selectAll("circle")
.data(data, d => d.id);
circles.enter()
.append("circle")
.attr("r", 4);
circles
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y));
circles.exit().remove();
}
Ключ обеспечивает:
Ключевая функция является центральным механизмом, связывающим:
Она определяет стратегию diff-алгоритма внутри D3 и фактически задаёт модель идентичности объектов, на которой строится вся система обновлений.