Одной из ключевых особенностей библиотеки D3.js является активное использование цепочек вызовов методов — method chaining. Этот подход позволяет строить последовательные операции над DOM-элементами в компактном, выразительном и читаемом виде.
Вместо хранения промежуточных результатов в отдельных переменных методы вызываются друг за другом, формируя логическую цепочку преобразований.
Большинство методов D3 возвращают объект selection. Благодаря этому следующий метод может быть вызван сразу после предыдущего.
Пример:
d3.select("body")
.append("div")
.attr("class", "container")
.style("color", "white")
.text("Пример цепочки");
Последовательность выполнения:
<body><div>Каждый метод возвращает новый или изменённый selection, что позволяет продолжать цепочку.
Цепочки работают потому, что методы D3 обычно возвращают:
Например:
const selection = d3.select("svg");
const result = selection.append("circle");
Метод append() возвращает новый selection, содержащий
созданный элемент <circle>.
Именно поэтому допустимо:
d3.select("svg")
.append("circle")
.attr("cx", 100)
.attr("cy", 100);
const svg = d3.select("svg");
const circle = svg.append("circle");
circle.attr("cx", 100);
circle.attr("cy", 100);
circle.attr("r", 50);
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);
Одно из наиболее мощных применений 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");
В этой цепочке происходит:
Все действия формируют единый декларативный поток.
Не все методы возвращают одинаковый объект.
.attr()
.style()
.classed()
.text()
Позволяют продолжать обычную цепочку.
.transition()
После вызова transition() цепочка переключается в режим
анимации.
d3.select("rect")
.transition()
.duration(500)
.attr("width", 300);
Теперь методы относятся к transition-объекту.
Некоторые методы прерывают chaining, потому что возвращают обычное значение.
Пример:
const width = d3.select("rect").attr("width");
Если attr() вызывается с одним аргументом, происходит
чтение значения, а не запись.
После этого продолжать цепочку невозможно:
d3.select("rect")
.attr("width")
.style("fill", "red");
Ошибка возникает потому, что .attr("width") возвращает
строку.
Многие методы работают в двух режимах.
Возвращает selection.
.attr("width", 200)
Возвращает значение.
.attr("width")
Это фундаментальная особенность API D3.
Особенно часто цепочки используются вместе с
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, который
поддерживает дальнейшие методы.
Современный стиль 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");
Иногда разделение на несколько цепочек улучшает читаемость.
Слишком длинные цепочки становятся трудными для восприятия.
Плохо:
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");
Подход полезен:
В D3 можно создавать собственные chainable-функции.
function setRed(selection) {
return selection.style("fill", "red");
}
Использование:
setRed(
d3.select("circle")
).attr("r", 40);
Функция возвращает selection, поэтому chaining продолжается.
Для интеграции пользовательских функций существует специальный метод
call().
function decorate(selection) {
selection
.attr("stroke", "black")
.attr("stroke-width", 2);
}
Пример:
d3.select("circle")
.call(decorate)
.attr("r", 50);
call():
Метод chaining лежит в основе компонентного подхода D3.
function chart(selection) {
selection
.append("rect")
.attr("width", 100)
.attr("height", 50);
}
Использование:
d3.select("svg")
.call(chart);
Так строятся:
Анимации в D3 полностью основаны на цепочках.
d3.select("circle")
.transition()
.duration(1000)
.delay(500)
.attr("r", 80)
.transition()
.duration(500)
.attr("fill", "red");
Последовательность:
Каждая transition() создаёт новый transition-объект.
d3.select("rect")
.transition()
.duration(1000)
.attr("x", 300)
.transition()
.duration(1000)
.attr("y", 200);
Анимации выполняются друг за другом.
Иногда chaining необходимо прервать.
Например:
const circles = d3.selectAll("circle");
Причины:
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);
});
Каждый обработчик формирует собственную цепочку.
Method chaining делает код D3 близким к декларативному и функциональному стилю.
Вместо последовательных императивных команд:
создать элемент
изменить атрибут
изменить стиль
добавить текст
создаётся единый поток операций:
d3.select(...)
.append(...)
.attr(...)
.style(...)
.text(...);
Это одна из причин высокой выразительности D3.
С точки зрения производительности chaining практически не даёт выигрыша сам по себе. Основная цель:
Однако chaining помогает избегать лишних переменных и делает код компактнее.
const width = d3.select("rect").attr("width");
width.attr("height", 50);
width содержит строку, а не selection.
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");
Особенности:
Цепочки вызовов являются не просто синтаксическим удобством, а частью архитектуры D3.
Большинство API библиотеки проектируется вокруг идеи:
Именно поэтому chaining используется практически во всех модулях D3: