d3.select и d3.selectAll: синтаксис и поведение

Методы d3.select() и d3.selectAll() являются основой механизма работы с DOM в библиотеке D3.js. Через них выполняется выбор элементов документа, последующая привязка данных, изменение атрибутов, стилей, текста и создание визуализаций.

Оба метода напоминают работу стандартных DOM API (querySelector и querySelectorAll), однако возвращают не обычные DOM-узлы, а специальные объекты — selection.


Метод d3.select

Общий синтаксис

d3.select(selector)

или

d3.select(node)

Выбор элемента по CSS-селектору

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

В данном случае выбирается первый найденный элемент <svg>.

Аналог:

document.querySelector("svg");

Однако d3.select() возвращает объект типа Selection, поддерживающий цепочки методов.


Возвращаемое значение

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

Объект selection содержит:

  • выбранный DOM-элемент;
  • внутренние группы элементов;
  • ссылки на родительские узлы;
  • методы D3 для манипуляций.

Пример:

d3.select("p")
  .text("Новый текст")
  .style("color", "red");

Выбор только первого элемента

Особенность d3.select():

d3.select(".item")

будет выбран только первый элемент с классом .item.

HTML:

<div class="item"></div>
<div class="item"></div>
<div class="item"></div>

Результат:

  • выбран только первый <div>.

Выбор DOM-узла напрямую

Метод принимает не только строку-селектор, но и готовый DOM-элемент.

const element = document.getElementById("box");

d3.select(element)
  .style("background", "blue");

Метод d3.selectAll

Общий синтаксис

d3.selectAll(selector)

или

d3.selectAll(nodes)

Выбор нескольких элементов

d3.selectAll("li")

Выбираются все элементы <li>.


Отличие от d3.select

d3.select

d3.select("li")

Выбирает:

только первый li

d3.selectAll

d3.selectAll("li")

Выбирает:

все li

Работа с группой элементов

d3.selectAll("p")
  .style("font-size", "20px");

Стиль применяется ко всем найденным <p>.


Selection в D3

Внутренняя структура

Объект selection хранит элементы в виде групп.

Упрощённая схема:

Selection
 ├── groups
 │    ├── [element1, element2, element3]
 └── parents

Это важно для:

  • вложенных выборок;
  • привязки данных;
  • enter/update/exit;
  • сложных визуализаций.

Поддержка CSS-селекторов

Методы поддерживают практически любые CSS-селекторы.

По тегу

d3.select("svg")

По классу

d3.select(".chart")

По идентификатору

d3.select("#container")

Вложенные селекторы

d3.select("div .item")

Комбинированные селекторы

d3.selectAll("ul li.active")

Атрибуты

d3.selectAll("[data-id]")

Цепочки методов

Одно из главных преимуществ D3 — chaining.

d3.select("h1")
  .text("D3.js")
  .style("color", "steelblue")
  .attr("class", "title");

Каждый метод возвращает selection.


Поведение при отсутствии элементов

d3.select

const s = d3.select(".unknown");

Если элемент не найден:

  • ошибка не возникает;
  • создаётся пустая selection.

Проверка:

s.empty()

Пример:

if (s.empty()) {
  console.log("Элемент не найден");
}

Поведение d3.selectAll

const items = d3.selectAll(".missing");

Возвращается пустая selection без ошибок.


Метод .node()

Позволяет получить обычный DOM-элемент.

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

Теперь доступны стандартные DOM API.


Метод .nodes()

Возвращает массив DOM-элементов.

const nodes = d3.selectAll("p").nodes();

Результат:

[
  p,
  p,
  p
]

Метод .size()

Количество выбранных элементов.

const count = d3.selectAll("div").size();

Метод .empty()

Проверка пустой selection.

d3.select("canvas").empty()

Вложенные выборки

Выбор внутри selection

const container = d3.select("#app");

container.select("p");

Поиск происходит только внутри #app.


Отличие глобального и локального поиска

Глобальный

d3.selectAll("p")

Поиск по всему документу.

Локальный

d3.select("#box")
  .selectAll("p")

Поиск только внутри #box.


Пример вложенной структуры

HTML:

<div id="chart">
  <p>A</p>
  <p>B</p>
</div>

<div id="info">
  <p>C</p>
</div>

Jav * aScript:

d3.select("#chart")
  .selectAll("p")
  .style("color", "red");

Красными станут только:

A
B

Selection и массивы

Важная особенность

Selection не является массивом.

Нельзя:

selection[0]

или

selection.map(...)

Получение массива элементов

const arr = d3.selectAll("div").nodes();

Теперь доступны методы массива:

arr.map(node => node.id);

Итерация через .each()

d3.selectAll("li")
  .each(function(d, i) {
    console.log(this);
    console.log(i);
  });

Аргументы

Аргумент Значение
d связанные данные
i индекс
this DOM-элемент

Контекст this

Внутри callback:

this === текущий DOM-элемент

Пример:

d3.selectAll("div")
  .each(function() {
    d3.select(this)
      .style("border", "1px solid black");
  });

Использование стрелочных функций

Следует учитывать:

d3.selectAll("div")
  .each(() => {
    console.log(this);
  });

Здесь this не будет ссылаться на DOM-элемент.

Причина — особенности lexical scope у стрелочных функций.


Selection и данные

select и selectAll тесно связаны с механизмом data binding.


Пример привязки данных

const data = [10, 20, 30];

d3.selectAll("p")
  .data(data)
  .text(d => d);

Каждый <p> получает своё значение.


Различия при привязке данных

select

d3.select("p").data([100]);

Связывается только один элемент.

selectAll

d3.selectAll("p").data([1,2,3]);

Связывается множество элементов.


Работа с enter selection

d3.select("body")
  .selectAll("p")
  .data(data)
  .enter()
  .append("p")
  .text(d => d);

Эта конструкция является одной из ключевых в D3.


Как работает selectAll перед .data()

Часто используется пустая выборка:

d3.select("svg")
  .selectAll("circle")

Даже если <circle> ещё нет.

Это необходимо для механизма:

  • enter;
  • update;
  • exit.

Selection как immutable-структура

Многие методы D3 возвращают новую selection.

Пример:

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

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

svg и circles — разные selection.


Группировка selection

Пример

d3.selectAll("g")
  .selectAll("circle")

D3 сохраняет структуру групп.

Упрощённо:

Group 1 -> circles
Group 2 -> circles
Group 3 -> circles

Это важно для:

  • иерархических данных;
  • вложенных SVG;
  • таблиц;
  • древовидных структур.

Selection и SVG

Наиболее часто методы используются с SVG.

Создание SVG

const svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);

Добавление элементов

svg.selectAll("circle")
  .data([30, 60, 90])
  .enter()
  .append("circle")
  .attr("cx", d => d)
  .attr("cy", 100)
  .attr("r", 20);

Selection и HTML

D3 работает не только с SVG.

d3.selectAll("div")
  .style("padding", "10px");

Смешивание D3 и обычного DOM API

Допустимо комбинировать подходы.

const button = document.querySelector("button");

d3.select(button)
  .text("Нажато");

Производительность selectAll

Особенности

d3.selectAll("*")

может быть дорогостоящей операцией.

Особенно при:

  • большом DOM;
  • частых обновлениях;
  • анимациях.

Оптимизация

Плохой вариант

d3.selectAll("div")

внутри каждого кадра анимации.

Лучше

const divs = d3.selectAll("div");

function update() {
  divs.style("opacity", 0.5);
}

Selection и порядок элементов

D3 сохраняет порядок элементов DOM.

d3.selectAll("li")

вернёт элементы в порядке документа.


Метод .filter()

Фильтрация selection.

d3.selectAll("p")
  .filter((d, i) => i % 2 === 0)
  .style("color", "red");

Метод .select() внутри selection

d3.selectAll("div")
  .select("p")

Важная особенность

Для каждого div выбирается только первый p.


Метод .selectAll() внутри selection

d3.selectAll("div")
  .selectAll("p")

Выбираются все <p> внутри каждого div.


Разница поведения

.select()

Сохраняет структуру групп.

.selectAll()

Создаёт новые подгруппы.

Это критически важно при работе с данными.


Пример группировки

HTML:

<div>
  <p>1</p>
  <p>2</p>
</div>

<div>
  <p>3</p>
</div>

Jav * aScript:

d3.selectAll("div")
  .selectAll("p")

Внутренне:

[
  [p, p],
  [p]
]

Selection и события

После выборки можно назначать обработчики.

d3.selectAll("button")
  .on("click", function() {
    d3.select(this)
      .style("background", "orange");
  });

Удаление элементов

d3.selectAll(".temp")
  .remove();

Добавление элементов

d3.select("body")
  .append("div")
  .text("Новый блок");

Частые ошибки

Ожидание массива

Неправильно:

d3.selectAll("p")[0]

Путаница между select и selectAll

Неправильно:

d3.select(".item")

если требуется обработать все элементы.


Использование this со стрелочными функциями

Неправильно:

.on("click", () => {
  d3.select(this)
})

Практический пример

HTML

<ul id="list"></ul>

JavaScript

const data = ["JavaScript", "D3", "SVG"];

d3.select("#list")
  .selectAll("li")
  .data(data)
  .enter()
  .append("li")
  .text(d => d);

Результат

<ul id="list">
  <li>JavaScript</li>
  <li>D3</li>
  <li>SVG</li>
</ul>

Отличия от jQuery

jQuery

$(".item")

Возвращает jQuery-коллекцию.

D3

d3.selectAll(".item")

Возвращает selection с поддержкой data binding.


Связь с enter/update/exit

Без selectAll() невозможен классический цикл обновления D3.

const circles = svg
  .selectAll("circle")
  .data(data);

Далее:

circles.enter()
circles.exit()

Современный подход .join()

Начиная с новых версий D3 часто используется:

svg.selectAll("circle")
  .data(data)
  .join("circle")

Однако основа всё равно строится на selectAll().


Внутренний механизм работы

d3.select

Использует:

querySelector()

d3.selectAll

Использует:

querySelectorAll()

После чего D3 оборачивает результат в собственную abstraction layer.


Ключевая концепция

select() и selectAll() — не просто способы поиска элементов.

Они формируют:

  • структуру selection;
  • иерархию групп;
  • основу data binding;
  • механизм обновления интерфейса;
  • модель работы всей библиотеки D3.