В D3.js работа с DOM строится вокруг концепции выборок (selections),
которые представляют собой наборы DOM-элементов, связанных с данными.
При построении визуализаций часто возникает необходимость объединить
несколько выборок в одну логическую цепочку обработки. Для этого
используется метод .merge(), который позволяет объединить
две выборки в единую структуру без потери контекста данных.
Метод .merge() не выполняет повторного связывания данных
и не изменяет исходные наборы элементов. Он лишь формирует новую
выборку, содержащую элементы из обеих групп, что особенно важно в
паттерне enter-update-exit.
В D3 существует три ключевых состояния элементов:
Метод .merge() применяется в основном для объединения
enter-выборки с update-выборкой, чтобы после
добавления новых элементов можно было применить общие стили и атрибуты
ко всей совокупности.
Ключевая идея заключается в следующем:
.enter().append().merge()Типичный паттерн выглядит следующим образом:
const update = svg.selectAll("circle")
.data(data);
// существующие элементы
update.attr("fill", "blue");
// новые элементы
const enter = update.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "green");
// объединение enter и update
enter.merge(update)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
Здесь .merge(update) создаёт единую выборку, к которой
применяются общие атрибуты. Это предотвращает дублирование кода и
гарантирует консистентность состояния DOM.
Игнорирование объединения выборок приводит к дублированию логики:
update.attr("cx", d => d.x)
.attr("cy", d => d.y);
enter.attr("cx", d => d.x)
.attr("cy", d => d.y);
Такая структура:
.merge() решает эту проблему, позволяя описывать общие
правила один раз.
Метод .merge() не клонирует DOM-элементы и не
пересоздаёт их. Он формирует новый объект selection, который:
Важно понимать, что это именно объединение ссылок, а не физическое перемещение или копирование элементов.
Наиболее корректная форма работы с .merge() проявляется
при полной синхронизации данных:
const circles = svg.selectAll("circle")
.data(data);
circles.exit()
.remove();
const enter = circles.enter()
.append("circle")
.attr("r", 0);
enter.merge(circles)
.transition()
.attr("r", 5)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
Здесь происходит последовательность:
.exit().remove().enter().append().merge()При объединении selection через .merge() важно
учитывать:
__data__Это означает, что .merge() работает только на уровне
структуры selection, а не на уровне data join.
Распространённая ошибка — игнорирование возвращаемого значения:
enter.merge(update)
.attr("fill", "red");
Если после merge не использовать результат, можно случайно продолжить работу с исходной выборкой.
Правильная форма:
const merged = enter.merge(update);
merged.attr("fill", "red");
Нельзя объединять произвольные выборки, не связанные одним data join:
const a = svg.selectAll("rect").data(dataA);
const b = svg.selectAll("circle").data(dataB);
a.merge(b); // логически некорректно
Такая конструкция нарушает модель данных, так как selections относятся к разным наборам DOM-элементов.
Метод .merge() особенно важен при работе с анимациями,
поскольку позволяет синхронизировать переходы для новых и существующих
элементов:
const bars = svg.selectAll("rect")
.data(data);
bars.exit()
.transition()
.attr("height", 0)
.remove();
const enter = bars.enter()
.append("rect")
.attr("height", 0);
enter.merge(bars)
.transition()
.attr("width", d => d.value)
.attr("height", d => d.value * 10);
В этом случае:
При построении сложных визуализаций часто используются вложенные selections:
const groups = svg.selectAll("g")
.data(data);
const groupsEnter = groups.enter()
.append("g");
groupsEnter.append("circle");
groupsEnter.merge(groups)
.attr("transform", d => `translate(${d.x}, ${d.y})`);
Здесь .merge() применяется к контейнерам
(g), что позволяет:
В современных версиях D3 часто используется .join(),
который инкапсулирует enter-update-exit логику. Однако
.merge() остаётся важной частью более низкоуровневого
управления выборками.
Разница заключается в том, что:
.join() автоматически вызывает .merge()
внутри.merge() предоставляет ручной контроль над
объединениемЭто делает .merge() более гибким инструментом в сложных
сценариях, где требуется тонкая настройка поведения элементов.
С точки зрения производительности:
.merge() не создаёт новых DOM-узловОсновная стоимость всё равно определяется:
append,
remove)transitionПоэтому .merge() является дешёвой операцией и может
использоваться свободно в каждом обновлении визуализации.
После объединения выборок важно помнить, что chain продолжает работу с новой selection:
enter.merge(update)
.attr("class", "node")
.style("opacity", 0.8);
Любые методы после .merge() применяются ко всем
элементам объединённой выборки, независимо от их происхождения (enter
или update).