Масштаб с временными значениями: d3.scaleTime()

D3.js предоставляет набор шкал (scales), среди которых временная шкала является ключевым инструментом для работы с датами и временными рядами. Функция d3.scaleTime() предназначена для преобразования значений типа Date в числовой диапазон пикселей, обеспечивая корректное отображение временных данных на графиках.

Временная шкала отличается от линейной тем, что её входные данные интерпретируются как объекты даты. Внутренне даты преобразуются в числовое представление (timestamp), однако API позволяет работать напрямую с объектами Date.

Основная идея:

  • domain — набор дат (начало и конец временного интервала)
  • range — диапазон координат (обычно пиксели на оси X)
const scale = d3.scaleTime()
  .domain([new Date(2024, 0, 1), new Date(2024, 11, 31)])
  .range([0, 800]);

Любая дата внутри указанного интервала будет линейно отображаться в диапазон от 0 до 800.


Внутренний принцип работы

d3.scaleTime() опирается на линейную интерполяцию временных значений. Каждая дата приводится к количеству миллисекунд с начала эпохи Unix:

  • 1 января 1970 года → 0
  • последующие даты → положительные числа
  • предыдущие даты → отрицательные числа

Преобразование можно описать так:

f(date) = a * t + b

где t — timestamp даты.


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

Временная шкала является двунаправленной:

  • scale(date) → координата
  • scale.invert(value) → дата
const scale = d3.scaleTime()
  .domain([new Date(2023, 0, 1), new Date(2023, 0, 10)])
  .range([0, 900]);

const x = scale(new Date(2023, 0, 5));
const date = scale.invert(450);

Обратное преобразование особенно важно при обработке событий мыши на графиках.


Работа с временными интервалами

Временные шкалы тесно связаны с интервалами времени:

  • дни
  • недели
  • месяцы
  • годы
  • часы, минуты, секунды

D3.js автоматически поддерживает корректное распределение таких интервалов при использовании осей (d3.axisBottom, d3.axisLeft).

const xScale = d3.scaleTime()
  .domain([new Date(2025, 0, 1), new Date(2025, 6, 1)])
  .range([0, 1000]);

const xAxis = d3.axisBottom(xScale)
  .ticks(d3.timeMonth.every(1));

Форматирование оси времени

Отображение дат на оси требует форматирования. Для этого используется d3.timeFormat():

const format = d3.timeFormat("%Y-%m");

const xAxis = d3.axisBottom(scale)
  .tickFormat(format);

Основные спецификаторы:

  • %Y — год
  • %m — месяц
  • %d — день
  • %H — часы
  • %M — минуты

Автоматическое вычисление делений (ticks)

Одной из сильных сторон временной шкалы является автоматический подбор “красивых” делений:

const axis = d3.axisBottom(scale)
  .ticks(d3.timeDay.every(2));

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

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

Локальное и UTC время

В D3.js существуют две версии временных шкал:

  • d3.scaleTime() — локальное время
  • d3.scaleUtc() — UTC без учёта часовых поясов

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

const local = d3.scaleTime();
const utc = d3.scaleUtc();

Использование UTC исключает сдвиги, вызванные локальными настройками системы.


Практический пример: временной ряд

const data = [
  { date: new Date(2024, 0, 1), value: 10 },
  { date: new Date(2024, 1, 1), value: 25 },
  { date: new Date(2024, 2, 1), value: 18 }
];

const x = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, 600]);

const y = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([300, 0]);

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


Особенности интерполяции и округления

Временная шкала не просто интерполирует значения, но и учитывает календарные особенности:

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

Это отличает её от простой линейной шкалы.


Комбинация с поведением zoom

При масштабировании графиков временная шкала используется как основа трансформации:

const zoomed = d3.event.transform.rescaleX(xScale);

Это позволяет сохранять корректное соответствие дат и координат при изменении масштаба.


Типичные ошибки при использовании

  • передача строк вместо объектов Date
  • смешивание локального времени и UTC
  • неверно заданный domain (например, строки вместо дат)
  • отсутствие сортировки данных по времени

Работа с пропусками данных

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

const sparseData = [
  { date: new Date(2024, 0, 1), value: 10 },
  { date: new Date(2024, 5, 1), value: 40 }
];

Расстояние между точками на графике будет пропорционально реальному времени между датами.


Сочетание с другими шкалами

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

  • d3.scaleLinear() — для значений
  • d3.scaleLog() — для экспоненциальных данных
  • d3.scaleBand() — для категорий по времени (например, дни недели)

Производительность при больших данных

D3.js эффективно обрабатывает большие временные наборы данных за счёт:

  • предвычисления интервалов
  • оптимизации tick generation
  • минимизации операций с объектами Date

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


Взаимодействие с пользовательским вводом

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

svg.on("mousemove", (event) => {
  const x = d3.pointer(event)[0];
  const date = scale.invert(x);
});

Это используется в интерактивных графиках, таймлайнах и финансовых приложениях.