Библиотека D3.js строится вокруг концепции выборок. Любой вызов
d3.select() или d3.selectAll() возвращает
объект selection — специальную структуру, содержащую ссылки на
DOM-элементы и методы для их обработки.
Простейшая выборка:
const svg = d3.select("svg");
Множественная выборка:
const circles = d3.selectAll("circle");
Каждый метод выборки возвращает новую выборку, благодаря чему формируются цепочки вызовов:
d3.select("svg")
.attr("width", 800)
.attr("height", 400)
.style("background", "#f5f5f5");
Главная особенность D3 заключается в том, что выборка может быть вложенной. Именно вложенные выборки позволяют строить сложные иерархические визуализации, таблицы, графики, группы SVG-элементов и повторяющиеся структуры.
Вложенная выборка возникает тогда, когда новый select()
или selectAll() вызывается относительно уже существующей
выборки.
Пример:
const rows = d3.select("table")
.selectAll("tr");
Сначала выбирается элемент <table>, затем внутри
него — все строки <tr>.
Следующий уровень вложенности:
const cells = d3.select("table")
.selectAll("tr")
.selectAll("td");
Здесь:
Каждый новый вызов работает относительно предыдущего контекста.
Внутри D3 выборка представляет собой массив групп элементов.
Упрощённо:
[
[element1, element2, element3]
]
После вложенного selectAll() структура становится
глубже:
[
[td1, td2],
[td3, td4],
[td5, td6]
]
Каждая внутренняя группа соответствует родительскому элементу.
Например:
d3.selectAll("tr")
.selectAll("td");
создаёт отдельную группу <td> для каждой строки
<tr>.
Это фундаментальная особенность D3, влияющая на:
this.Метод select() выбирает только первый подходящий элемент
внутри каждого родителя.
d3.selectAll(".card")
.select("h2");
Если имеется 10 карточек, будет выбрано 10 заголовков — по одному из каждой карточки.
Метод selectAll() выбирает все совпадающие элементы
внутри каждого родителя.
d3.selectAll(".card")
.selectAll("p");
Теперь внутри каждой карточки будут выбраны все абзацы.
Ключевая идея:
selection.selectAll(...)
не ищет элементы глобально по документу.
Поиск происходит только внутри текущей выборки.
Пример:
const groups = d3.selectAll("g");
groups.selectAll("circle");
Каждая группа <g> ищет собственные
<circle>.
Это особенно важно при работе с SVG:
const chart = d3.select("svg");
const groups = chart
.selectAll("g")
.data(data)
.enter()
.append("g");
Теперь каждая группа может содержать собственные линии, текст и фигуры:
groups.append("circle");
groups.append("text");
D3 автоматически наследует данные во вложенных выборках.
Пример:
const data = [
[10, 20, 30],
[40, 50, 60]
];
const rows = d3.select("body")
.selectAll("div.row")
.data(data)
.enter()
.append("div");
Каждая строка получает массив:
[10, 20, 30]
или:
[40, 50, 60]
Следующий уровень:
rows.selectAll("span")
.data(d => d)
.enter()
.append("span")
.text(d => d);
Что происходит:
rows содержит строки;data(d => d) возвращает массив текущей строки;Результат:
<div class="row">
<span>10</span>
<span>20</span>
<span>30</span>
</div>
<div class="row">
<span>40</span>
<span>50</span>
<span>60</span>
</div>
Во многих callback-функциях D3 предоставляет специальный контекст
this.
Пример:
d3.selectAll("rect")
.attr("width", function(d) {
console.log(this);
});
this указывает на текущий DOM-элемент.
Это позволяет обращаться к элементу напрямую:
d3.select(this)
.style("fill", "red");
Контекст this работает в:
attr()style()property()classed()text()html()each()filter()on()data()Пример:
selection.each(function(d, i) {
console.log(this);
});
Большинство callback-функций D3 получают:
function(d, i, nodes)
Где:
| Аргумент | Значение |
|---|---|
d |
данные элемента |
i |
индекс |
nodes |
массив DOM-элементов |
Пример:
d3.selectAll("circle")
.attr("cx", function(d, i, nodes) {
console.log(d);
console.log(i);
console.log(nodes);
});
При этом:
this === nodes[i]
D3 активно использует контекст исполнения функций.
Пример:
selection.on("click", function() {
d3.select(this)
.classed("active", true);
});
Без this пришлось бы выполнять повторный поиск
элемента.
Контекст делает обработку:
Стрелочные функции не создают собственный this.
Ошибка:
selection.on("click", (d) => {
console.log(this);
});
Здесь this не указывает на DOM-элемент.
Вместо этого он наследуется из внешней области видимости.
Правильный вариант:
selection.on("click", function(d) {
console.log(this);
});
Если this не используется, стрелочные функции подходят
отлично.
Пример:
selection.attr("width", d => d.value * 10);
Здесь контекст элемента не нужен.
selection.style("color", function() {
return this.dataset.color;
});
this — текущий элемент.
selection.style("color", () => {
return this.dataset.color;
});
Ошибка: this не является элементом.
Метод each() особенно часто применяется с
this.
Пример:
selection.each(function(d, i) {
const element = d3.select(this);
element.append("title")
.text(d.name);
});
Каждый элемент обрабатывается отдельно.
Во вложенных структурах this всегда относится к текущему
элементу конкретной группы.
Пример:
rows.selectAll("td")
.style("color", function() {
console.log(this);
});
this указывает именно на конкретную ячейку
<td>.
Часто требуется доступ к родителю.
Пример:
selection.each(function() {
console.log(this.parentNode);
});
Можно получить выборку родителя:
const parent = d3.select(this.parentNode);
Через this можно выполнять локальные выборки:
selection.each(function() {
d3.select(this)
.select("text")
.style("font-weight", "bold");
});
Это особенно удобно в компонентах графиков.
Обработчики событий — одна из самых важных областей использования
this.
Пример:
d3.selectAll("circle")
.on("mouseenter", function() {
d3.select(this)
.attr("r", 20);
});
При наведении изменяется только текущий круг.
Современный синтаксис D3:
selection.on("click", function(event, d) {
console.log(event);
console.log(d);
console.log(this);
});
Где:
| Значение | Содержимое |
|---|---|
event |
объект события |
d |
данные |
this |
DOM-элемент |
Контекст сохраняется и внутри анимаций.
Пример:
selection.transition()
.duration(1000)
.attr("x", function() {
return this.dataset.target;
});
Типичная структура D3-графика:
<svg>
<g class="axis"></g>
<g class="bars"></g>
<g class="labels"></g>
</svg>
Создание:
const svg = d3.select("svg");
const barsGroup = svg.append("g")
.attr("class", "bars");
barsGroup.selectAll("rect")
.data(data)
.enter()
.append("rect");
Здесь:
barsGroup становится контекстом;.bars.d3.selectAll("rect");
Поиск по всему документу.
group.selectAll("rect");
Поиск только внутри группы.
Локальные выборки:
Вложенные выборки позволяют строить независимые визуальные компоненты.
Пример:
function createLegend(container, data) {
const items = container
.selectAll(".legend-item")
.data(data)
.enter()
.append("g");
items.append("rect");
items.append("text");
}
Использование:
createLegend(
d3.select("#legend"),
legendData
);
Все операции происходят внутри переданного контейнера.
Индекс i относится только к текущей группе.
Пример:
rows.selectAll("td")
.text(function(d, i) {
return i;
});
Каждая строка начнёт индексацию заново:
0 1 2
0 1 2
0 1 2
Это следствие групповой структуры вложенных выборок.
Третий аргумент callback-функции:
function(d, i, nodes)
позволяет получить всю текущую группу элементов.
Пример:
selection.attr("x", function(d, i, nodes) {
if (i === nodes.length - 1) {
return 100;
}
return 0;
});
Метод filter() также использует this.
selection.filter(function(d) {
return this.dataset.active === "true";
});
Можно комбинировать данные и DOM-состояние.
call() позволяет передавать выборку в функцию.
function highlight(selection) {
selection
.style("stroke", "red");
}
Использование:
d3.selectAll("circle")
.call(highlight);
Во вложенных структурах:
groups.call(highlight);
Каждая группа остаётся независимой.
Ошибка:
selection.each(() => {
d3.select(this);
});
Правильно:
selection.each(function() {
d3.select(this);
});
Ошибка:
selection.each(function() {
setTimeout(function() {
console.log(this);
});
});
Внутри setTimeout контекст меняется.
Исправление:
selection.each(function() {
const element = this;
setTimeout(function() {
console.log(element);
});
});
Ошибка:
d3.selectAll("text")
.style("fill", "red");
Изменятся все тексты документа.
Безопаснее:
group.selectAll("text")
.style("fill", "red");
Создание таблицы:
const data = [
["A", "B", "C"],
["D", "E", "F"]
];
const table = d3.select("body")
.append("table");
const rows = table
.selectAll("tr")
.data(data)
.enter()
.append("tr");
rows.selectAll("td")
.data(d => d)
.enter()
.append("td")
.text(d => d);
Механизм работы:
d3.selectAll("td")
.on("click", function(event, d) {
d3.select(this)
.style("background", "orange");
console.log(d);
});
При клике изменяется только выбранная ячейка.
Практически любой сложный график в D3 строится через вложенные группы:
svg
└── g.chart
├── g.axis-x
├── g.axis-y
├── g.bars
└── g.labels
Каждая группа становится отдельным контекстом выборки.
Это обеспечивает:
| Особенность | Описание |
|---|---|
| Локальный контекст | Поиск элементов внутри родителя |
| Групповая структура | Выборки организованы по группам |
| Наследование данных | Дочерние элементы получают данные родителя |
| Локальные индексы | Индексы считаются внутри группы |
| Независимость компонентов | Группы работают автономно |
| Особенность | Описание |
|---|---|
| Указывает на DOM-элемент | Текущий элемент обработки |
| Работает только с function | Не работает со стрелочными функциями |
| Доступен в callbacks | attr, style, each, on и др. |
| Упрощает локальную обработку | Нет необходимости в повторных селекторах |
| Связан с группой | Контекст зависит от текущей выборки |