Интерполяция значений: d3-interpolate

Модуль d3-interpolate предназначен для вычисления промежуточных значений между двумя состояниями. Интерполяция лежит в основе анимаций, плавных переходов, морфинга фигур, изменения цветов, шкалирования и обновления визуализаций.

В экосистеме D3 интерполяция используется практически везде:

  • в переходах (d3-transition);
  • при анимации атрибутов SVG;
  • при изменении цветов;
  • при построении шкал;
  • при обновлении данных;
  • при морфинге путей;
  • в пользовательских вычислениях.

Главная идея библиотеки — получение функции-интерполятора.

const interpolate = d3.interpolate(a, b)

После этого функция принимает значение от 0 до 1:

interpolate(t)

где:

  • 0 — начальное состояние;
  • 1 — конечное состояние;
  • промежуточные значения дают промежуточный результат.

Простейшая числовая интерполяция

interpolate()

const i = d3.interpolate(10, 50)

console.log(i(0))    // 10
console.log(i(0.5))  // 30
console.log(i(1))    // 50

Функция автоматически определяет тип данных и выбирает подходящий механизм интерполяции.

Формула линейной интерполяции:

v(t)=a+(b-a)t

где:

  • a — начальное значение;
  • b — конечное значение;
  • t — коэффициент интерполяции от 0 до 1.

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

interpolateNumber()

const interpolateNumber = d3.interpolateNumber(0, 100)

console.log(interpolateNumber(0.25))
console.log(interpolateNumber(0.75))

Результат:

25
75

Этот метод используется:

  • для координат;
  • размеров;
  • прозрачности;
  • толщины линий;
  • вычисления кадров анимации.

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

interpolateString()

Библиотека умеет извлекать числа из строк и плавно изменять их.

const i = d3.interpolateString(
    "translate(0, 0)",
    "translate(200, 100)"
)

console.log(i(0.5))

Результат:

translate(100, 50)

Это особенно важно для SVG-трансформаций.


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

interpolateRgb()

const color = d3.interpolateRgb("red", "blue")

console.log(color(0))
console.log(color(0.5))
console.log(color(1))

Интерполяция происходит в пространстве RGB.


interpolateHsl()

const color = d3.interpolateHsl("yellow", "green")

Интерполяция в HSL даёт более естественные переходы оттенков.


interpolateLab()

const color = d3.interpolateLab("#ff0000", "#0000ff")

Цветовое пространство LAB лучше подходит для визуально равномерных переходов.


interpolateCubehelix()

const color = d3.interpolateCubehelix(
    "purple",
    "orange"
)

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


Цветовые схемы и градиенты

Интерполяция часто применяется совместно с последовательными шкалами.

const scale = d3.scaleSequential()
    .domain([0, 100])
    .interpolator(d3.interpolateViridis)

Теперь каждое число преобразуется в цвет.

console.log(scale(10))
console.log(scale(90))

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

interpolateArray()

const i = d3.interpolateArray(
    [0, 10, 20],
    [100, 200, 300]
)

console.log(i(0.5))

Результат:

[50, 105, 160]

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


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

interpolateObject()

const i = d3.interpolateObject(
    { x: 0, y: 0 },
    { x: 100, y: 200 }
)

console.log(i(0.5))

Результат:

{
    x: 50,
    y: 100
}

Очень полезно для:

  • конфигураций графиков;
  • состояний интерфейсов;
  • координат узлов;
  • анимированных данных.

Интерполяция дат

interpolateDate()

const start = new Date(2024, 0, 1)
const end = new Date(2024, 11, 31)

const i = d3.interpolateDate(start, end)

console.log(i(0.5))

Возвращается промежуточная дата.


Интерполяция трансформаций SVG

interpolateTransformSvg()

const i = d3.interpolateTransformSvg(
    "translate(0,0) scale(1)",
    "translate(300,100) scale(2)"
)

console.log(i(0.5))

Результат:

translate(150,50) scale(1.5)

Метод умеет корректно разбирать:

  • translate;
  • scale;
  • rotate;
  • skewX;
  • matrix.

Интерполяция CSS-трансформаций

interpolateTransformCss()

const i = d3.interpolateTransformCss(
    "translateX(0px)",
    "translateX(400px)"
)

Используется для HTML-элементов.


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

interpolateDiscrete()

Вместо плавного изменения выбираются значения из набора.

const i = d3.interpolateDiscrete([
    "small",
    "medium",
    "large"
])

console.log(i(0.2))
console.log(i(0.8))

Подходит для:

  • категорий;
  • состояний интерфейса;
  • текстовых переходов.

Квантованная интерполяция

quantize()

Метод создаёт массив значений интерполяции.

const values = d3.quantize(
    d3.interpolateNumber(0, 1),
    5
)

console.log(values)

Результат:

[0, 0.25, 0.5, 0.75, 1]

Часто используется:

  • в генерации палитр;
  • создании шагов;
  • дискретизации анимаций.

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

Наиболее часто d3-interpolate используется внутри переходов.

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

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


Пользовательские tween-функции

attrTween()

d3.select("circle")
    .transition()
    .attrTween("cx", function() {

        const i = d3.interpolateNumber(0, 300)

        return function(t) {
            return i(t)
        }
    })

Здесь интерполяция создаётся вручную.


styleTween()

d3.select("div")
    .transition()
    .styleTween("background-color", function() {

        const i = d3.interpolateRgb(
            "red",
            "blue"
        )

        return function(t) {
            return i(t)
        }
    })

Интерполяция SVG-path

Одно из самых сложных направлений.

const path1 = "M0,0L100,0L100,100"
const path2 = "M0,0L200,50L100,200"

const i = d3.interpolateString(path1, path2)

Однако корректный морфинг работает только при совместимой структуре путей.

Проблемы возникают, если:

  • разное число сегментов;
  • разные типы команд;
  • несовместимые кривые.

Для сложного морфинга часто используются:

  • flubber;
  • d3-interpolate-path.

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

Интерполяция отвечает за вычисление значений, а easing — за характер движения.

Без easing:

t = 0 → 1

С easing:

ease(t)

Пример:

d3.select("rect")
    .transition()
    .ease(d3.easeBounce)
    .attr("x", 400)

Процесс:

  1. easing изменяет время;
  2. интерполятор вычисляет значение;
  3. D3 обновляет атрибут.

Нестандартные интерполяторы

Пользовательская функция

function customInterpolator(a, b) {

    return function(t) {
        return a + Math.sin(t) * (b - a)
    }
}

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

const i = customInterpolator(0, 100)

console.log(i(0.5))

Интерполяция сложных структур

D3 умеет рекурсивно обрабатывать вложенные объекты.

const i = d3.interpolate(
    {
        position: { x: 0, y: 0 },
        color: "red"
    },
    {
        position: { x: 100, y: 200 },
        color: "blue"
    }
)

Внутри будут автоматически созданы:

  • числовые интерполяторы;
  • цветовые интерполяторы;
  • объектные интерполяторы.

Производительность интерполяции

При больших объёмах данных важны:

  • количество создаваемых интерполяторов;
  • сложность объектов;
  • частота кадров;
  • число DOM-обновлений.

Оптимизация

Повторное использование функций

Плохо:

for (let i = 0; i < 10000; i++) {
    d3.interpolateNumber(0, 100)
}

Лучше:

const interpolate = d3.interpolateNumber(0, 100)

for (let i = 0; i < 10000; i++) {
    interpolate(Math.random())
}

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

Интерполяция обычно быстрая. Основные затраты происходят при:

  • изменении DOM;
  • перерисовке SVG;
  • layout/reflow браузера.

Интерполяция и шкалы

Многие шкалы D3 используют интерполяцию внутри.

const scale = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 500])

Фактически шкала выполняет:

genui{“math_block_widget_always_prefetch_v2”:{“content”:“y=r_0+(r_1-r_0)”}}

где:

  • d0, d1 — домен;
  • r0, r1 — диапазон.

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

d3-interpolate не зависит от SVG и DOM.

const interpolateX = d3.interpolateNumber(0, 500)

function render(t) {

    const x = interpolateX(t)

    ctx.clearRect(0, 0, width, height)

    ctx.beginPath()
    ctx.arc(x, 100, 20, 0, Math.PI * 2)
    ctx.fill()
}

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

D3-интерполяторы можно использовать вне D3-анимаций.

const i = d3.interpolateNumber(0, 300)

const start = performance.now()

function animate(time) {

    const elapsed = time - start

    const t = Math.min(elapsed / 1000, 1)

    const value = i(t)

    console.log(value)

    if (t < 1) {
        requestAnimationFrame(animate)
    }
}

requestAnimationFrame(animate)

Интерполяция и data join

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

selection
    .data(data)
    .transition()
    .attr("height", d => d.value)

Старые значения плавно переходят в новые.


Основные категории интерполяторов

Интерполятор Назначение
interpolateNumber Числа
interpolateRgb RGB-цвета
interpolateHsl HSL-цвета
interpolateLab LAB-цвета
interpolateString Строки
interpolateArray Массивы
interpolateObject Объекты
interpolateDate Даты
interpolateTransformSvg SVG-transform
interpolateTransformCss CSS-transform
interpolateDiscrete Дискретные значения

Практический пример анимации графика

const svg = d3.select("svg")

svg.append("circle")
    .attr("cx", 50)
    .attr("cy", 100)
    .attr("r", 20)
    .transition()
    .duration(2000)
    .ease(d3.easeCubic)
    .attr("cx", 400)
    .attr("fill", "orange")

Внутри D3 создаёт:

  • интерполятор координаты;
  • интерполятор цвета;
  • временной интерполятор easing;
  • механизм обновления кадров.

Архитектура работы интерполяции в D3

Полный процесс выглядит так:

  1. Определяется тип данных.
  2. Выбирается подходящий интерполятор.
  3. Создаётся функция (t) => value.
  4. Во время анимации вычисляется текущее t.
  5. Интерполятор возвращает промежуточное состояние.
  6. DOM или Canvas обновляется.

Особенности автоматической интерполяции

Метод d3.interpolate() самостоятельно определяет типы.

d3.interpolate(0, 100)
d3.interpolate("red", "blue")
d3.interpolate([1,2], [5,6])
d3.interpolate({x:0}, {x:100})

Это делает API чрезвычайно гибким.


Ограничения d3-interpolate

Отсутствие физической симуляции

Интерполяция не учитывает:

  • массу;
  • инерцию;
  • столкновения;
  • ускорение.

Для этого используется:

  • d3-force;
  • пользовательская физика;
  • игровые движки.

Несовместимые структуры

Проблемы возникают при:

d3.interpolate(
    [1,2,3],
    [1,2]
)

или:

d3.interpolate(
    {x:10},
    {y:20}
)

Результаты могут быть непредсказуемыми.


Связь с другими модулями D3

d3-transition

Использует интерполяцию для анимаций.


d3-scale

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


d3-color

Предоставляет цветовые пространства.


d3-ease

Изменяет временную функцию интерполяции.


Наиболее распространённые сценарии применения

Анимация диаграмм

  • столбчатые графики;
  • круговые диаграммы;
  • линейные графики.

Плавное обновление данных

  • дашборды;
  • мониторинг;
  • realtime-графики.

Цветовые карты

  • heatmap;
  • choropleth;
  • density visualization.

Морфинг интерфейсов

  • панели;
  • карточки;
  • SVG-иконки.

Роль интерполяции в декларативной визуализации

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

  • начальное состояние;
  • конечное состояние.

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

Именно это делает D3 мощным инструментом анимированной визуализации данных.