Цепочки вызовов (method chaining)

Одной из ключевых особенностей библиотеки D3.js является активное использование цепочек вызовов методов — method chaining. Этот подход позволяет строить последовательные операции над DOM-элементами в компактном, выразительном и читаемом виде.

Вместо хранения промежуточных результатов в отдельных переменных методы вызываются друг за другом, формируя логическую цепочку преобразований.


Основной принцип method chaining

Большинство методов D3 возвращают объект selection. Благодаря этому следующий метод может быть вызван сразу после предыдущего.

Пример:

d3.select("body")
  .append("div")
  .attr("class", "container")
  .style("color", "white")
  .text("Пример цепочки");

Последовательность выполнения:

  1. Выбирается элемент <body>
  2. Внутрь добавляется <div>
  3. Устанавливается класс
  4. Применяется CSS-стиль
  5. Добавляется текст

Каждый метод возвращает новый или изменённый selection, что позволяет продолжать цепочку.


Возврат объекта selection

Цепочки работают потому, что методы D3 обычно возвращают:

  • текущий selection;
  • новый selection;
  • transition;
  • join-объект.

Например:

const selection = d3.select("svg");

const result = selection.append("circle");

Метод append() возвращает новый selection, содержащий созданный элемент <circle>.

Именно поэтому допустимо:

d3.select("svg")
  .append("circle")
  .attr("cx", 100)
  .attr("cy", 100);

Отличие от обычного пошагового подхода

Без chaining

const svg = d3.select("svg");

const circle = svg.append("circle");

circle.attr("cx", 100);
circle.attr("cy", 100);
circle.attr("r", 50);

С chaining

d3.select("svg")
  .append("circle")
  .attr("cx", 100)
  .attr("cy", 100)
  .attr("r", 50);

Второй вариант:

  • компактнее;
  • легче читается;
  • показывает последовательность действий сверху вниз;
  • уменьшает количество промежуточных переменных.

Читаемость длинных цепочек

В D3 длинные цепочки являются стандартным стилем написания кода.

Правильное форматирование особенно важно:

d3.select("svg")
  .append("rect")
  .attr("x", 20)
  .attr("y", 30)
  .attr("width", 200)
  .attr("height", 100)
  .attr("fill", "steelblue")
  .attr("stroke", "black")
  .style("opacity", 0.8);

Каждый метод располагается на новой строке, благодаря чему:

  • визуально видна последовательность операций;
  • упрощается поиск ошибок;
  • легче изменять отдельные параметры.

Категории методов, участвующих в цепочках

Методы выбора

d3.select()
d3.selectAll()

Пример:

d3.select("body")
  .selectAll("p");

Методы создания элементов

.append()
.insert()
.join()

Пример:

d3.select("svg")
  .append("g")
  .append("circle");

Методы изменения атрибутов

.attr()
.style()
.property()
.classed()
.text()
.html()

Пример:

d3.select("circle")
  .attr("r", 40)
  .style("fill", "orange");

Методы работы с данными

.data()
.datum()
.enter()
.exit()

Пример:

d3.select("svg")
  .selectAll("circle")
  .data(data)
  .enter()
  .append("circle");

Методы анимации

.transition()
.duration()
.delay()
.ease()

Пример:

d3.select("circle")
  .transition()
  .duration(1000)
  .attr("r", 100);

Цепочки и data binding

Одно из наиболее мощных применений chaining — связывание данных с DOM-элементами.

const data = [10, 20, 30, 40];

d3.select("svg")
  .selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", (d, i) => i * 80 + 50)
  .attr("cy", 100)
  .attr("r", d => d)
  .attr("fill", "steelblue");

В этой цепочке происходит:

  1. Выбор SVG
  2. Выбор всех окружностей
  3. Привязка массива данных
  4. Создание недостающих элементов
  5. Добавление окружностей
  6. Установка атрибутов

Все действия формируют единый декларативный поток.


Возврат разных типов объектов

Не все методы возвращают одинаковый объект.

Возврат selection

.attr()
.style()
.classed()
.text()

Позволяют продолжать обычную цепочку.


Возврат transition

.transition()

После вызова transition() цепочка переключается в режим анимации.

d3.select("rect")
  .transition()
  .duration(500)
  .attr("width", 300);

Теперь методы относятся к transition-объекту.


Возврат значения вместо selection

Некоторые методы прерывают chaining, потому что возвращают обычное значение.

Пример:

const width = d3.select("rect").attr("width");

Если attr() вызывается с одним аргументом, происходит чтение значения, а не запись.

После этого продолжать цепочку невозможно:

d3.select("rect")
  .attr("width")
  .style("fill", "red");

Ошибка возникает потому, что .attr("width") возвращает строку.


Getter и Setter в D3

Многие методы работают в двух режимах.

Setter

Возвращает selection.

.attr("width", 200)

Getter

Возвращает значение.

.attr("width")

Это фундаментальная особенность API D3.


Chaining и enter selection

Особенно часто цепочки используются вместе с enter().

d3.select("svg")
  .selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", (d, i) => i * 40)
  .attr("y", 20)
  .attr("width", 30)
  .attr("height", d => d);

enter() возвращает специальный enter-selection, который поддерживает дальнейшие методы.


Использование join()

Современный стиль D3 предполагает использование join() вместо явного enter().

d3.select("svg")
  .selectAll("circle")
  .data(data)
  .join("circle")
  .attr("cx", (d, i) => i * 50)
  .attr("cy", 100)
  .attr("r", d => d);

Цепочка становится:

  • короче;
  • проще;
  • легче для сопровождения.

Вложенные цепочки

Цепочки могут комбинироваться.

const group = d3.select("svg")
  .append("g")
  .attr("transform", "translate(100, 100)");

group
  .append("circle")
  .attr("r", 50);

group
  .append("text")
  .text("D3");

Иногда разделение на несколько цепочек улучшает читаемость.


Когда chaining ухудшает код

Слишком длинные цепочки становятся трудными для восприятия.

Плохо:

d3.select("svg").append("g").attr("class", "group")
  .append("rect").attr("x", 10).attr("y", 20)
  .attr("width", 200).attr("height", 100)
  .attr("fill", "blue").style("opacity", 0.5);

Проблемы:

  • трудно редактировать;
  • сложно искать ошибки;
  • нарушается визуальная структура.

Разделение цепочек

Иногда полезно сохранять промежуточный selection.

const rect = d3.select("svg")
  .append("rect");

rect
  .attr("width", 200)
  .attr("height", 100);

rect
  .style("fill", "green")
  .style("stroke", "black");

Подход полезен:

  • при сложной логике;
  • при повторном использовании selection;
  • при условных изменениях;
  • при отладке.

Chainability пользовательских функций

В D3 можно создавать собственные chainable-функции.

function setRed(selection) {
  return selection.style("fill", "red");
}

Использование:

setRed(
  d3.select("circle")
).attr("r", 40);

Функция возвращает selection, поэтому chaining продолжается.


Метод call()

Для интеграции пользовательских функций существует специальный метод call().

function decorate(selection) {
  selection
    .attr("stroke", "black")
    .attr("stroke-width", 2);
}

Пример:

d3.select("circle")
  .call(decorate)
  .attr("r", 50);

call():

  • передаёт selection в функцию;
  • возвращает исходный selection;
  • не прерывает цепочку.

Chaining и переиспользуемые компоненты

Метод chaining лежит в основе компонентного подхода D3.

function chart(selection) {
  selection
    .append("rect")
    .attr("width", 100)
    .attr("height", 50);
}

Использование:

d3.select("svg")
  .call(chart);

Так строятся:

  • графические компоненты;
  • оси;
  • легенды;
  • диаграммы;
  • reusable charts.

Работа с transition chaining

Анимации в D3 полностью основаны на цепочках.

d3.select("circle")
  .transition()
  .duration(1000)
  .delay(500)
  .attr("r", 80)
  .transition()
  .duration(500)
  .attr("fill", "red");

Последовательность:

  1. Первая анимация изменения радиуса
  2. Вторая анимация изменения цвета

Каждая transition() создаёт новый transition-объект.


Последовательные transition

d3.select("rect")
  .transition()
  .duration(1000)
  .attr("x", 300)
  .transition()
  .duration(1000)
  .attr("y", 200);

Анимации выполняются друг за другом.


Прерывание цепочек

Иногда chaining необходимо прервать.

Например:

const circles = d3.selectAll("circle");

Причины:

  • повторное использование selection;
  • оптимизация;
  • условная логика;
  • обработка событий;
  • сложные вычисления.

Chaining в обработчиках событий

d3.selectAll("circle")
  .on("mouseover", function () {
    d3.select(this)
      .attr("fill", "orange")
      .attr("r", 50);
  })
  .on("mouseout", function () {
    d3.select(this)
      .attr("fill", "steelblue")
      .attr("r", 30);
  });

Каждый обработчик формирует собственную цепочку.


Функциональный стиль D3

Method chaining делает код D3 близким к декларативному и функциональному стилю.

Вместо последовательных императивных команд:

создать элемент
изменить атрибут
изменить стиль
добавить текст

создаётся единый поток операций:

d3.select(...)
  .append(...)
  .attr(...)
  .style(...)
  .text(...);

Это одна из причин высокой выразительности D3.


Влияние chaining на производительность

С точки зрения производительности chaining практически не даёт выигрыша сам по себе. Основная цель:

  • улучшение читаемости;
  • уменьшение шаблонного кода;
  • упрощение описания визуализации.

Однако chaining помогает избегать лишних переменных и делает код компактнее.


Распространённые ошибки

Потеря selection

const width = d3.select("rect").attr("width");

width.attr("height", 50);

width содержит строку, а не selection.


Неверный контекст после transition

d3.select("circle")
  .transition()
  .attr("r", 50)
  .append("title");

После transition() объект уже не является обычным selection.


Слишком длинные цепочки

Код становится трудно поддерживать:

d3.select(...).append(...).attr(...).style(...)

без переносов строк и структуры.


Практический стиль написания

Наиболее распространённый стиль D3-кода:

d3.select("svg")
  .selectAll("rect")
  .data(data)
  .join("rect")
  .attr("x", (d, i) => i * 35)
  .attr("y", d => 200 - d)
  .attr("width", 30)
  .attr("height", d => d)
  .attr("fill", "teal");

Особенности:

  • одна операция — одна строка;
  • визуальная последовательность;
  • отсутствие лишних переменных;
  • декларативное описание структуры.

Chainable API как архитектурный принцип D3

Цепочки вызовов являются не просто синтаксическим удобством, а частью архитектуры D3.

Большинство API библиотеки проектируется вокруг идеи:

  • выбрать элементы;
  • преобразовать selection;
  • вернуть selection;
  • продолжить обработку.

Именно поэтому chaining используется практически во всех модулях D3:

  • selections;
  • transitions;
  • scales;
  • axes;
  • shapes;
  • force simulation;
  • zoom;
  • drag;
  • brush.