Концепция выборок (selections) в D3

Выборки (selections) — центральная концепция библиотеки D3.js. Практически любая операция в D3 строится вокруг выборки: получение элементов DOM, изменение атрибутов, работа с данными, анимации, обработка событий и создание визуализаций.

Выборка представляет собой объект, содержащий один или несколько DOM-элементов, к которым затем применяются операции D3.

Основная идея заключается в декларативном управлении документом: вместо ручного обхода DOM библиотека позволяет описывать, что именно должно измениться.


Создание выборок

Метод d3.select()

Метод select() выбирает первый элемент, соответствующий CSS-селектору.

d3.select("body")

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

d3.select(".container")

Выбор элемента по идентификатору:

d3.select("#chart")

После выбора можно выполнять операции:

d3.select("#title")
    .text("D3.js Selections")

В данном примере:

  • найден элемент с id="title"
  • содержимое элемента заменено через text()

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

Метод d3.selectAll()

Для работы с набором элементов используется selectAll().

d3.selectAll("p")

Теперь выборка содержит все элементы <p>.

Пример изменения стилей:

d3.selectAll("p")
    .style("color", "blue")

Все абзацы станут синими.


Структура объекта Selection

Объект selection не является обычным массивом. Это специальная структура D3, содержащая:

  • группы элементов
  • ссылки на родителей
  • методы цепочек
  • механизмы привязки данных

Пример:

const selection = d3.selectAll("div")

Объект selection хранит внутренние структуры, которыми управляет D3.


Цепочки вызовов

D3 активно использует chaining — цепочки вызовов методов.

d3.select("#box")
    .style("width", "200px")
    .style("height", "200px")
    .style("background", "steelblue")

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

Преимущества:

  • компактность кода
  • читаемость
  • декларативный стиль
  • последовательное применение изменений

Изменение содержимого элементов

Метод text()

Изменяет текстовое содержимое.

d3.select("#message")
    .text("Hello D3")

Получение текста:

const text = d3.select("#message").text()

Метод html()

Позволяет вставлять HTML.

d3.select("#content")
    .html("<strong>Bold text</strong>")

Следует учитывать риск XSS при работе с пользовательскими данными.


Работа с атрибутами

Метод attr()

Используется для изменения HTML- и SVG-атрибутов.

d3.select("img")
    .attr("src", "image.png")

Пример для SVG:

d3.select("circle")
    .attr("cx", 100)
    .attr("cy", 50)
    .attr("r", 40)

Получение значения:

const radius = d3.select("circle").attr("r")

Работа со стилями

Метод style()

Изменяет CSS-свойства.

d3.select("#panel")
    .style("background-color", "lightgray")

Несколько стилей:

d3.select("#panel")
    .style("padding", "20px")
    .style("border", "1px solid black")

Получение значения:

const color = d3.select("#panel")
    .style("background-color")

Работа с CSS-классами

Метод classed()

Добавляет или удаляет классы.

Добавление класса:

d3.select("#item")
    .classed("active", true)

Удаление:

d3.select("#item")
    .classed("active", false)

Проверка:

const isActive = d3.select("#item")
    .classed("active")

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

Метод append()

Создаёт новый дочерний элемент.

d3.select("body")
    .append("p")
    .text("New paragraph")

Создание SVG:

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

Добавление круга:

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

Вставка элементов

Метод ins ert()

Позволяет вставить элемент перед другим.

d3.select("body")
    .insert("p", ":first-child")
    .text("Inserted paragraph")

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

Метод remove()

Удаляет элементы из DOM.

d3.select("#old")
    .remove()

Удаление группы элементов:

d3.selectAll(".unused")
    .remove()

Обход вложенных элементов

Выборки могут быть вложенными.

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

Сначала выбирается контейнер, затем все абзацы внутри него.


Иерархия выборок

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

Пример:

d3.selectAll(".card")
    .select("h2")

Для каждого .card будет найден свой h2.

Это важное отличие от обычного DOM API.


Перебор элементов

Метод each()

Позволяет выполнить функцию для каждого элемента.

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

Параметры:

  • d — данные
  • i — индекс

this внутри функции указывает на DOM-элемент.


Получение DOM-элемента

Метод node()

Возвращает первый DOM-элемент выборки.

const node = d3.select("#chart").node()

Метод nodes()

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

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

Проверка существования элемента

Метод empty()

const isEmpty = d3.select(".missing").empty()

Возвращает true, если элемент не найден.


Размер выборки

Метод size()

const count = d3.selectAll("p").size()

Фильтрация элементов

Метод filter()

Фильтрация по условию:

d3.selectAll("p")
    .filter(function(d, i) {
        return i % 2 === 0
    })
    .style("color", "red")

Красными станут только чётные элементы.


Сортировка элементов

Метод sort()

d3.selectAll("div")
    .sort((a, b) => a.val ue - b.value)

Используется совместно с привязанными данными.


Объединение выборок

Метод merge()

Часто применяется после enter().

enterSelection.merge(updateSelection)

Позволяет объединить новые и существующие элементы.


Концепция data join

Одна из ключевых особенностей D3 — связывание данных с DOM.

const data = [10, 20, 30]

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

Теперь каждый элемент связан со значением массива.


Метод data()

Пример:

d3.select("body")
    .selectAll("p")
    .data([1, 2, 3])
    .text(d => d)

d — текущее значение данных.


Enter Selection

Если данных больше, чем элементов DOM, используется enter-selection.

const data = [1, 2, 3]

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

Процесс:

  1. Выполняется выборка <p>
  2. D3 сравнивает количество элементов и данных
  3. Для отсутствующих элементов создаётся enter-selection
  4. Через append() создаются новые узлы

Update Selection

Если элемент уже существует, D3 обновляет его.

d3.selectAll("p")
    .data([5, 10, 15])
    .text(d => d)

Exit Selection

Если элементов больше, чем данных:

d3.selectAll("p")
    .data([1])
    .exit()
    .remove()

Лишние элементы удаляются.


Полный цикл enter-update-exit

const data = [10, 20, 30]

const selection = d3.select("body")
    .selectAll("p")
    .data(data)

selection.enter()
    .append("p")
    .merge(selection)
    .text(d => d)

selection.exit().remove()

Этот механизм является фундаментом динамических визуализаций.


Функция ключей

D3 позволяет указывать ключ для сопоставления элементов.

.data(data, d => d.id)

Ключи особенно важны:

  • при анимациях
  • при перестановке элементов
  • при обновлении графиков

Без ключей D3 использует индекс массива.


Локальный контекст выбора

Во многих методах используется this.

d3.selectAll("button")
    .each(function() {
        d3.select(this)
            .style("color", "red")
    })

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


Выборка внутри обработчиков событий

d3.selectAll("button")
    .on("click", function() {
        d3.select(this)
            .classed("active", true)
    })

Работа с событиями

Метод on()

d3.select("#btn")
    .on("click", function(event) {
        console.log(event)
    })

Поддерживаются любые DOM-события:

  • click
  • mouseover
  • mouseout
  • input
  • keydown
  • drag

Функциональные значения

Большинство методов D3 могут принимать функцию.

d3.selectAll("p")
    .style("font-size", (d, i) => `${i * 2 + 12}px`)

D3 автоматически вызывает функцию для каждого элемента.


Аргументы callback-функций

Стандартный набор:

function(d, i, nodes)

Где:

  • d — данные
  • i — индекс
  • nodes — массив элементов

Selection как основа визуализаций

Практически любой график в D3 строится через выборки.

Пример столбчатой диаграммы:

const data = [30, 80, 45, 60]

d3.select("svg")
    .selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", (d, i) => i * 60)
    .attr("y", d => 200 - d)
    .attr("width", 50)
    .attr("height", d => d)

Иммутабельность выборок

Многие операции возвращают новую выборку.

const divs = d3.selectAll("div")

const filtered = divs.filter(".active")

filtered — новая выборка.


Группы внутри selection

D3 internally хранит элементы в группах.

Например:

d3.selectAll(".row")
    .selectAll(".cell")

Каждая группа .cell относится к своему .row.

Это позволяет строить сложные иерархические структуры.


Selection и SVG

Выборки особенно важны при работе с SVG.

const svg = d3.select("svg")

svg.append("line")
    .attr("x1", 0)
    .attr("y1", 0)
    .attr("x2", 100)
    .attr("y2", 100)

D3 одинаково работает:

  • с HTML
  • с SVG
  • с Canvas-контейнерами
  • с XML-документами

Метод call()

Позволяет передать selection в функцию.

function highlight(selection) {
    selection.style("border", "2px solid red")
}

d3.selectAll(".box")
    .call(highlight)

Полезно для повторного использования логики.


Поведение методов getter/setter

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

Setter:

selection.attr("width", 500)

Getter:

selection.attr("width")

Поведение зависит от количества аргументов.


Selection и transitions

Анимации D3 строятся поверх выборок.

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

transition() создаёт специальную transition-selection.


Типичный шаблон работы

Наиболее распространённый шаблон:

d3.select(container)
    .selectAll(elements)
    .data(data)
    .join(elementType)

Метод join()

Современный API D3 упрощает enter-update-exit.

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

D3 автоматически:

  • создаёт новые элементы
  • обновляет существующие
  • удаляет лишние

Расширенный join()

selection.join(
    enter => enter.append("circle"),
    update => update,
    exit => exit.remove()
)

Позволяет управлять всеми стадиями жизненного цикла.


Selection и производительность

Основные рекомендации:

  • минимизировать количество DOM-операций
  • использовать chaining
  • избегать лишних выборок
  • переиспользовать selection
  • использовать ключи в data()

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

Повторные выборки

Плохо:

d3.select("#chart")
d3.select("#chart")
d3.select("#chart")

Лучше:

const chart = d3.select("#chart")

Отсутствие exit()

Без удаления старых элементов DOM начинает разрастаться.


Неверный контекст this

Стрелочные функции не создают собственный this.

Проблема:

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

Корректно:

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

Связь selection и декларативного подхода

D3 описывает состояние интерфейса через данные.

Главная идея:

данные → выборка → DOM

DOM становится отражением структуры данных.

Именно концепция selection делает возможными:

  • динамические визуализации
  • интерактивные графики
  • анимированные диаграммы
  • реактивные обновления
  • сложные SVG-интерфейсы