Концепция переходов в D3: .transition()

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

Основой механизма анимации служит метод .transition().


Метод .transition()

Метод .transition() создаёт объект перехода для выбранных элементов. После вызова свойства элементов изменяются не мгновенно, а постепенно.

Простейший пример:

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

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

Без перехода:

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

изменение происходит мгновенно.


Как работает переход

После вызова .transition() библиотека:

  1. фиксирует текущее состояние элемента;
  2. определяет конечное состояние;
  3. вычисляет промежуточные значения;
  4. обновляет свойства на каждом кадре анимации.

D3 использует интерполяцию значений и синхронизирует обновления через requestAnimationFrame.


Основные свойства переходов

.duration()

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

d3.select("circle")
    .transition()
    .duration(2000)
    .attr("r", 80);

Радиус круга изменится за 2 секунды.


.delay()

Устанавливает задержку перед началом анимации.

d3.select("circle")
    .transition()
    .delay(1000)
    .attr("r", 80);

Переход начнётся через 1 секунду.


.ease()

Определяет функцию плавности.

d3.select("circle")
    .transition()
    .ease(d3.easeBounce)
    .attr("cy", 300);

D3 содержит множество встроенных easing-функций.


Встроенные функции плавности

Линейная анимация

.ease(d3.easeLinear)

Скорость постоянна на протяжении всего перехода.


Квадратичная плавность

.ease(d3.easeQuad)

Плавное ускорение.


Кубическая плавность

.ease(d3.easeCubic)

Более мягкое изменение скорости.


Эффект отскока

.ease(d3.easeBounce)

Создаёт имитацию прыжка или падения.


Эффект упругости

.ease(d3.easeElastic)

Добавляет колебания.


Анимация атрибутов

Переходы часто используются для изменения SVG-атрибутов.

Изменение размеров

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

Изменение координат

d3.select("circle")
    .transition()
    .attr("cx", 500)
    .attr("cy", 150);

Изменение цвета

d3.select("rect")
    .transition()
    .style("fill", "orange");

D3 автоматически интерполирует цвета.


Интерполяция значений

Внутри переходов работает система интерполяторов.

Например:

.attr("x", 500)

означает постепенное изменение значения от текущего x до 500.

Для цветов:

.style("fill", "red")

D3 вычисляет промежуточные оттенки автоматически.


Интерполяция чисел

d3.interpolate(0, 100)(0.5)

Результат:

50

Интерполяция цветов

d3.interpolateRgb("red", "blue")(0.5)

Возвращает промежуточный цвет.


Интерполяция строк

D3 умеет извлекать числа даже из строк.

d3.interpolateString(
    "translate(0,0)",
    "translate(100,50)"
)

Переходы и selection

Метод .transition() преобразует обычную selection в transition-selection.

const t = d3.selectAll("rect")
    .transition();

После этого доступны методы переходов:

  • .duration()
  • .delay()
  • .ease()
  • .on()

Цепочки переходов

Переходы поддерживают chaining.

d3.select("circle")
    .transition()
    .duration(1000)
    .attr("cx", 300)
    .style("fill", "green");

Все изменения выполняются одновременно.


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

Можно выстраивать анимации последовательно.

d3.select("circle")
    .transition()
    .duration(1000)
    .attr("cx", 300)
    .transition()
    .duration(1000)
    .attr("cy", 200);

Сначала изменится cx, затем cy.


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

Для независимых анимаций используются разные transition-объекты.

const t1 = d3.transition()
    .duration(1000);

const t2 = d3.transition()
    .duration(3000);

d3.select("circle")
    .transition(t1)
    .attr("cx", 300);

d3.select("rect")
    .transition(t2)
    .attr("width", 500);

Именованные переходы

D3 поддерживает именование transition.

.transition("move")

Это позволяет управлять конкретными анимациями.

d3.select("circle")
    .transition("move")
    .attr("cx", 300);

Прерывание переходов

Новый transition с тем же именем прерывает предыдущий.

d3.select("circle")
    .transition("move")
    .attr("cx", 100);

d3.select("circle")
    .transition("move")
    .attr("cx", 500);

Второй переход остановит первый.


Метод .interrupt()

Позволяет вручную остановить анимацию.

d3.select("circle")
    .interrupt();

События переходов

Transition поддерживает события жизненного цикла.


Событие начала

.transition()
.on("start", function() {
    console.log("Старт");
});

Событие завершения

.transition()
.on("end", function() {
    console.log("Завершение");
});

Событие прерывания

.transition()
.on("interrupt", function() {
    console.log("Прервано");
});

Работа с this

В обработчиках событий this указывает на текущий DOM-элемент.

.on("end", function() {
    d3.select(this)
        .style("fill", "red");
});

Переходы с данными

Особенно важны transition при обновлении визуализации.

svg.selectAll("rect")
    .data(data)
    .transition()
    .attr("height", d => d);

Высота столбцов будет изменяться плавно.


Анимация обновления столбчатой диаграммы

svg.selectAll("rect")
    .data(data)
    .join("rect")
    .transition()
    .duration(1000)
    .attr("x", (d, i) => i * 50)
    .attr("y", d => 300 - d)
    .attr("height", d => d)
    .attr("width", 40);

Enter transition

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

svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("r", 0)
    .transition()
    .attr("r", 20);

Круги появляются с анимацией увеличения.


Exit transition

Удаление элементов также можно анимировать.

svg.selectAll("circle")
    .data(data)
    .exit()
    .transition()
    .attr("r", 0)
    .remove();

Transition и .remove()

Метод .remove() в transition удаляет элемент только после завершения анимации.

.transition()
.style("opacity", 0)
.remove();

Анимация прозрачности

d3.select("text")
    .transition()
    .style("opacity", 0);

Часто используется для fade-in и fade-out эффектов.


Плавное появление

d3.select("text")
    .style("opacity", 0)
    .transition()
    .style("opacity", 1);

Transition и transform

Переходы отлично работают с SVG-transform.

.transition()
.attr("transform", "translate(300,100)");

Анимация масштабирования

.transition()
.attr("transform", "scale(2)");

Анимация вращения

.transition()
.attr("transform", "rotate(180)");

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

Метод .attrTween() позволяет полностью контролировать процесс интерполяции.

.transition()
.attrTween("cx", function() {

    return function(t) {
        return 100 + t * 300;
    };

});

t изменяется от 0 до 1.


Метод .styleTween()

Аналогично работает со стилями.

.transition()
.styleTween("fill", function() {

    return d3.interpolateRgb(
        "red",
        "blue"
    );

});

Метод .tween()

Создание произвольной анимации.

.transition()
.tween("custom", function() {

    return function(t) {
        console.log(t);
    };

});

Transition и SVG path

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

path.transition()
    .attr("d", line(newData));

D3 интерполирует путь между состояниями.


Морфинг графиков

Плавное изменение формы графика:

svg.select("path")
    .datum(newData)
    .transition()
    .duration(1500)
    .attr("d", line);

Transition и оси

Оси также поддерживают анимацию.

axisGroup
    .transition()
    .call(d3.axisLeft(scale));

Изменения шкалы отображаются плавно.


Анимация сортировки

Transition особенно полезны при перестройке диаграмм.

bars.transition()
    .attr("x", (d, i) => x(i));

Столбцы перемещаются в новые позиции.


Staggered animation

Эффект каскадной анимации достигается через функцию в .delay().

.transition()
.delay((d, i) => i * 100)
.attr("height", d => d);

Элементы запускаются по очереди.


Использование функции в .duration()

.transition()
.duration((d, i) => i * 200);

Каждый элемент получает собственную длительность.


Комбинирование delay и duration

.transition()
.delay((d, i) => i * 50)
.duration(500)
.ease(d3.easeCubic);

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

Анимации могут становиться дорогими при большом количестве элементов.

Основные причины снижения производительности:

  • частые изменения layout;
  • большое количество DOM-узлов;
  • сложные SVG-path;
  • чрезмерная длительность анимаций.

Оптимизация анимаций

Минимизация DOM-изменений

Лучше изменять transform, чем геометрию элемента.

.attr("transform", ...)

Ограничение количества элементов

SVG плохо масштабируется на десятки тысяч узлов.


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

Для сложных анимаций иногда используют Canvas вместо SVG.


Асинхронная природа transition

Transition не блокируют выполнение кода.

d3.select("circle")
    .transition()
    .duration(1000);

console.log("Код продолжает работу");

Promise и transition

Современные версии D3 позволяют работать с завершением перехода через .end().

await d3.select("circle")
    .transition()
    .attr("cx", 300)
    .end();

Ошибка прерывания

Если transition был остановлен, .end() выбросит исключение.

try {

    await transition.end();

} catch(e) {

    console.log("Переход прерван");

}

Transition и timer

Внутри D3 transition построены поверх системы таймеров.

d3.timer(callback);

Однако .transition() предоставляет более высокий уровень абстракции.


Отличия transition от CSS-анимаций

Преимущества D3 transition

  • интеграция с данными;
  • динамические интерполяторы;
  • работа с SVG;
  • управление enter/update/exit;
  • программируемые tween-функции.

Преимущества CSS-анимаций

  • аппаратное ускорение;
  • простота;
  • высокая производительность для UI;
  • компактность.

Когда использовать transition

Transition особенно эффективны в:

  • интерактивных графиках;
  • dashboard-системах;
  • анимированных диаграммах;
  • визуализации потоков данных;
  • географических картах;
  • временных шкалах.

Типичная схема обновления визуализации

selection
    .data(data)
    .join(
        enter => enter
            .append("rect")
            .attr("height", 0)
            .transition()
            .attr("height", d => d),

        update => update
            .transition()
            .attr("height", d => d),

        exit => exit
            .transition()
            .attr("height", 0)
            .remove()
    );

Эта архитектура лежит в основе большинства динамических визуализаций в D3.