Цепочки переходов: .transition().transition()

Модель выполнения переходов

В D3.js переходы (transitions) реализуют механизм плавного изменения свойств DOM-элементов во времени. Каждый вызов .transition() создаёт новый объект перехода, который управляет интерполяцией значений атрибутов и стилей.

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

Каждый переход характеризуется:

  • длительностью (duration)
  • задержкой (delay)
  • функцией сглаживания (ease)
  • набором интерполируемых свойств (attr, style, text, tween)

Базовое поведение .transition()

При вызове:

d3.select("circle")
  .transition()
  .attr("r", 50);

создаётся переход первого уровня. Он захватывает текущее состояние элемента и вычисляет промежуточные значения до конечного состояния.

Если после этого добавить второй .transition(), возникает важный эффект:

d3.select("circle")
  .transition()
  .attr("r", 50)
  .transition()
  .attr("fill", "red");

Здесь формируется цепочка последовательных переходов.


Семантика цепочки .transition().transition()

Каждый вызов .transition() на результате предыдущего перехода создаёт новый независимый переход, который:

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

Таким образом формируется последовательность анимационных фаз.

Механизм можно представить как конвейер:

Transition A → Transition B → Transition C

Каждый следующий переход ждёт завершения предыдущего.


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

При цепочке:

d3.select("rect")
  .transition()
  .duration(1000)
  .attr("width", 200)
  .transition()
  .duration(500)
  .attr("height", 100);

поведение следующее:

  1. Первый переход изменяет width за 1000 мс
  2. После завершения автоматически стартует второй переход
  3. Второй переход изменяет height за 500 мс

Важно, что второй .transition() создаёт новый объект перехода, а не расширяет первый.


Разделение состояний интерполяции

Каждый переход фиксирует стартовое состояние в момент своего запуска. Это означает:

  • второй переход не наследует промежуточные значения первого
  • он берёт фактическое текущее состояние DOM после завершения первого перехода

Это принципиально отличает D3-переходы от CSS-анимаций, где возможны кумулятивные эффекты трансформаций.


Поведение при множественных .transition()

Рассмотрим расширенную цепочку:

d3.select("circle")
  .transition()
  .attr("r", 20)
  .transition()
  .attr("cx", 100)
  .transition()
  .style("fill", "blue");

Формируется линейная последовательность из трёх фаз:

  1. изменение радиуса
  2. перемещение по оси X
  3. изменение цвета

Каждый этап выполняется строго после завершения предыдущего.


Отличие цепочек переходов от параллельных переходов

Цепочка:

selection
  .transition()
  .attr("r", 10)
  .transition()
  .attr("r", 20);

Последовательное выполнение.

Параллельные переходы:

selection
  .transition("a")
  .attr("r", 10);

selection
  .transition("b")
  .attr("fill", "red");

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


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

Если два последовательных перехода изменяют один и тот же атрибут, применяется последний активный переход:

d3.select("rect")
  .transition()
  .attr("width", 100)
  .transition()
  .attr("width", 300);

Результат:

  • первый переход задаёт промежуточное состояние
  • второй полностью переопределяет финальное значение

Фактически итоговое значение определяется последним переходом в цепочке.


Внутренняя модель исполнения

Каждый .transition() создаёт объект типа Transition, который:

  • регистрируется в таймере D3
  • получает стартовое время t0
  • вычисляет интерполяцию через функцию времени t

При цепочке:

T1.start → T1.end → T2.start → T2.end

D3 автоматически ставит T2 в очередь исполнения, используя завершение T1 как триггер.


Влияние delay на цепочки

Задержка применяется к каждому переходу независимо:

d3.select("circle")
  .transition()
  .delay(500)
  .attr("r", 30)
  .transition()
  .delay(200)
  .attr("fill", "green");

Фактическое время исполнения:

  • первый переход стартует через 500 мс
  • второй стартует через 200 мс после завершения первого

Важно: delay второго перехода не суммируется с первым, а применяется отдельно к его старту.


Влияние duration на последовательность

Длительность каждого перехода формирует общую временную структуру цепочки:

Ttotal = duration1 + duration2 + ... + durationN

При этом переходы не перекрываются, если явно не заданы независимые таймеры.


Интерполяция между цепочками

Каждый переход вычисляет интерполяцию заново:

  • фиксируется начальное значение
  • фиксируется конечное значение
  • строится функция t → value

При переходе к следующему .transition() предыдущая функция уничтожается и заменяется новой.


Прерывание цепочек

Если в процессе выполнения цепочки вызывается новый переход на тот же selection:

const sel = d3.select("circle");

sel.transition()
  .attr("r", 10);

setTimeout(() => {
  sel.transition()
    .attr("r", 50);
}, 200);

поведение:

  • активный переход прерывается
  • текущее значение фиксируется как новое стартовое
  • запускается новый переход

Это разрушает цепочку и создаёт новую ветку анимации.


Типичные паттерны использования цепочек

Последовательное построение сцены

d3.select("g")
  .transition()
  .attr("transform", "translate(100,0)")
  .transition()
  .style("opacity", 1)
  .transition()
  .attr("transform", "translate(200,0)");

Используется для поэтапного раскрытия визуализации.


Анимация появления объектов

d3.selectAll("circle")
  .transition()
  .attr("r", 5)
  .transition()
  .style("opacity", 1);

Каждый этап отвечает за отдельную фазу визуального появления.


Пошаговое изменение геометрии

d3.select("path")
  .transition()
  .attr("d", line1)
  .transition()
  .attr("d", line2)
  .transition()
  .attr("d", line3);

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


Ограничения цепочек .transition().transition()

  • невозможно параллельно управлять несколькими фазами внутри одной цепочки
  • каждый переход полностью блокирует следующий до завершения
  • отсутствует встроенный механизм синхронизации ветвлений внутри цепочки

Поведение с selection.transition и transition.transition

Цепочка:

selection.transition().transition()

отличается от:

const t = selection.transition();
t.transition();

В обоих случаях создаётся новый переход, но контекст выполнения может отличаться при работе с именованными переходами и группами элементов.


Итоговая модель поведения цепочек

Цепочка .transition().transition() в D3.js представляет собой последовательный исполнительный механизм, где:

  • каждый переход является независимым объектом
  • переходы образуют линейную очередь
  • состояние DOM фиксируется между фазами
  • интерполяции не пересекаются
  • каждый новый .transition() перезапускает модель времени

Такая структура позволяет строить детерминированные анимационные сценарии с чётко заданной последовательностью изменений.