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

Выделение DOM-элементов в D3.js строится вокруг концепции селекций — абстракции над набором узлов документа, позволяющей применять к ним операции как к единой группе. Селекции являются фундаментом всей библиотеки, поскольку через них выполняется связывание данных, изменение атрибутов, стилизация и обработка событий.

Селекция создаётся через методы:

  • d3.select() — выбирает первый подходящий элемент
  • d3.selectAll() — выбирает все элементы, соответствующие селектору CSS
const container = d3.select("#chart");
const circles = d3.selectAll("circle");

Каждая селекция возвращает объект, содержащий массив DOM-узлов и методы для их модификации. При этом операции выполняются не по одному элементу, а сразу по всей группе.

Ключевая особенность — цепочечный интерфейс. Практически любой метод возвращает новую селекцию, что позволяет строить последовательные преобразования:

d3.select("svg")
  .attr("width", 600)
  .attr("height", 400)
  .style("background-color", "#f5f5f5");

Привязка данных к элементам

Селекции становятся особенно мощными при связывании с данными через метод data().

const data = [10, 20, 30];

d3.selectAll("circle")
  .data(data);

После привязки каждый элемент получает доступ к соответствующему значению. Это используется для динамического построения визуализаций.

Методы enter(), update() и exit() управляют жизненным циклом элементов:

  • enter() — новые данные без соответствующих DOM-элементов
  • update() — существующие элементы
  • exit() — лишние элементы
const circles = d3.select("svg")
  .selectAll("circle")
  .data(data);

circles.enter()
  .append("circle")
  .attr("r", d => d);

Атрибуты и стили

Изменение внешнего вида выполняется через attr() и style().

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

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

Атрибуты могут задаваться функциями, принимающими данные и индекс элемента:

d3.selectAll("circle")
  .attr("r", (d, i) => d * 2);

Стилизация

d3.selectAll("circle")
  .style("fill", "steelblue")
  .style("opacity", 0.8);

Функциональный подход позволяет динамически вычислять стили:

.style("fill", d => d > 50 ? "red" : "green");

Обработка событий

Селекции позволяют назначать обработчики событий напрямую:

d3.selectAll("circle")
  .on("click", function(event, d) {
    console.log("Значение:", d);
  });

Контекст this внутри обработчика указывает на DOM-элемент, а event содержит информацию о событии.

Наиболее часто используемые события:

  • mouseover
  • mouseout
  • mousemove
  • click

Подсветка при наведении

Подсветка элементов при наведении реализуется через сочетание событий mouseover и mouseout. Основная идея заключается в изменении стилей активного элемента и возврате исходного состояния при уходе курсора.

Базовый вариант

d3.selectAll("circle")
  .on("mouseover", function() {
    d3.select(this)
      .style("fill", "orange")
      .style("stroke", "black")
      .style("stroke-width", 2);
  })
  .on("mouseout", function() {
    d3.select(this)
      .style("fill", "steelblue")
      .style("stroke", "none");
  });

Здесь используется d3.select(this), позволяющий выделить текущий элемент из всей группы.

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

Более устойчивый подход основан на управлении CSS-классами:

d3.selectAll("circle")
  .on("mouseover", function() {
    d3.select(this).classed("hovered", true);
  })
  .on("mouseout", function() {
    d3.select(this).classed("hovered", false);
  });

CSS:

.hovered {
  fill: orange;
  stroke: black;
  stroke-width: 2px;
}

Такой способ отделяет логику от представления и упрощает поддержку.

Взаимодействие с группами элементов

Подсветка может затрагивать не только один элемент, но и связанные элементы. Это достигается через использование данных.

d3.selectAll("circle")
  .on("mouseover", function(event, d) {
    d3.selectAll("circle")
      .style("opacity", c => c === d ? 1 : 0.3);
  })
  .on("mouseout", function() {
    d3.selectAll("circle")
      .style("opacity", 1);
  });

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

Передача индексов и контекста

Каждая функция в D3 получает три аргумента:

  • d — данные
  • i — индекс элемента
  • nodes — массив узлов
d3.selectAll("circle")
  .attr("cx", (d, i) => i * 40 + 20);

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

Управление переходами при наведении

Подсветка становится более выразительной при использовании переходов:

d3.selectAll("circle")
  .on("mouseover", function() {
    d3.select(this)
      .transition()
      .duration(200)
      .attr("r", 12);
  })
  .on("mouseout", function() {
    d3.select(this)
      .transition()
      .duration(200)
      .attr("r", 6);
  });

Плавные изменения визуального состояния улучшают восприятие интерактивности.

Делегирование событий

При большом количестве элементов используется делегирование через общий контейнер:

d3.select("svg")
  .on("mouseover", function(event) {
    const target = event.target;
    if (target.tagName === "circle") {
      d3.select(target)
        .style("fill", "orange");
    }
  });

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

Сравнение стратегий подсветки

Существует несколько подходов:

  • Прямое изменение стилей — быстрый, но менее управляемый
  • Через классы CSS — структурированный и расширяемый
  • Через данные — позволяет учитывать контекст и взаимосвязи элементов
  • Через контейнерное событие — оптимально для больших визуализаций

Выбор зависит от сложности сцены и объёма данных.

Типичные ошибки при работе с hover-эффектами

  • Перезапись исходных стилей без возможности восстановления
  • Назначение слишком большого числа обработчиков на отдельные элементы
  • Игнорирование состояния переходов, приводящее к «дёрганию» визуализации
  • Использование фиксированных значений вместо вычисляемых функций

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