Легенда и форматирование дат

Визуализации временных рядов в Nivo опираются на корректную работу с датами на уровне шкал, осей, подсказок и легенд. Основная сложность заключается не в отображении чисел, а в согласованном преобразовании дат между данными, масштабами D3 и форматированием UI-слоёв.

Nivo ожидает, что временные значения будут приведены к одному из двух форматов:

  • Date-объект JavaScript
  • ISO-строка, которую можно преобразовать в дату

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

const data = [
  {
    id: "Температура",
    data: [
      { x: new Date("2024-01-01"), y: 12 },
      { x: new Date("2024-01-02"), y: 15 },
      { x: new Date("2024-01-03"), y: 9 }
    ]
  }
];

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

xScale={{
  type: "time",
  format: "%Y-%m-%d",
  precision: "day"
}}

Здесь формат задаётся в стиле d3-time-format, что важно для согласованного парсинга.

Форматирование дат через d3-time-format

Внутри Nivo используется экосистема D3, поэтому форматирование дат опирается на d3-time-format.

Основные символы:

  • %Y — год (2024)
  • %m — месяц (01–12)
  • %d — день месяца
  • %H — часы
  • %M — минуты
  • %S — секунды

Пример функции форматирования:

import { format } from "d3-time-format";

const formatDate = format("%d.%m.%Y");

Эта функция используется в нескольких слоях: оси, подсказки, легенды.

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

Ось X в временных графиках — основной потребитель форматирования дат. В Nivo это задаётся через axisBottom или axisTop.

axisBottom={{
  format: value => {
    const date = new Date(value);
    return `${date.getDate()}.${date.getMonth() + 1}`;
  }
}}

Более строгий подход — использование d3-time-format:

import { format } from "d3-time-format";

const axisFormat = format("%d.%m");

axisBottom={{
  format: axisFormat
}}

При работе с большими диапазонами времени важно учитывать масштаб:

  • дни → %d.%m
  • месяцы → %b %Y
  • годы → %Y

Это снижает визуальный шум и улучшает читаемость.

Time scale и точность отображения

Шкала времени в Nivo опирается на D3 scaleTime:

xScale={{
  type: "time",
  precision: "day",
  useUTC: false
}}

Параметр precision влияет на:

  • агрегацию точек
  • интервал между тиками
  • поведение интерполяции

Частая ошибка — смешивание временных зон. При работе с глобальными данными используется useUTC: true, чтобы избежать сдвигов дат.

Подсказки (tooltip) и форматирование дат

Tooltip в Nivo позволяет полностью контролировать представление даты:

tooltip={({ point }) => {
  const date = new Date(point.data.x);
  return (
    <div>
      <div>{date.toLocaleDateString()}</div>
      <div>{point.data.y}</div>
    </div>
  );
}}

Для строгого форматирования предпочтительно использовать единый формат:

const formatFullDate = format("%d.%m.%Y %H:%M");

tooltip={({ point }) => (
  <div>
    <div>{formatFullDate(new Date(point.data.x))}</div>
    <div>{point.data.y}</div>
  </div>
)}

Tooltip часто становится местом, где проявляется несогласованность форматов, если оси и легенды используют разные правила отображения.

Легенды и связь с временными данными

Легенды в Nivo обычно отражают идентификаторы серий, но в временных визуализациях часто возникает задача включения дат в подписи.

Базовая конфигурация:

legends={[
  {
    anchor: "top-left",
    direction: "column",
    itemWidth: 120,
    itemHeight: 20,
    symbolSize: 12
  }
]}

По умолчанию легенда отображает id серии, но форматирование возможно через dataFrom и трансформацию данных.

Динамическое форматирование легенд с датами

Если серии представляют периоды времени, подписи можно формировать вручную:

legends={[
  {
    anchor: "bottom",
    direction: "row",
    dataFrom: "keys",
    itemWidth: 100,
    itemHeight: 20,
    translateY: 50,
    format: value => {
      const date = new Date(value);
      return format("%d.%m")(date);
    }
  }
]}

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

Группировка данных по времени и влияние на легенду

В реальных сценариях данные часто агрегируются:

  • по дням
  • по неделям
  • по месяцам

Пример структуры:

const data = [
  {
    id: "2024-01",
    data: [
      { x: "2024-01-01", y: 100 },
      { x: "2024-01-02", y: 120 }
    ]
  }
];

Легенда в этом случае должна отражать период:

legends={[
  {
    anchor: "top",
    direction: "row",
    itemWidth: 90,
    format: value => {
      const [year, month] = value.split("-");
      return `${month}.${year}`;
    }
  }
]}

Локализация дат в легендах и осях

При отображении данных для разных регионов важно учитывать локаль:

const formatLocalized = new Intl.DateTimeFormat("ru-RU", {
  day: "2-digit",
  month: "short",
  year: "numeric"
});

axisBottom={{
  format: value => formatLocalized.format(new Date(value))
}}

Локализация влияет не только на формат, но и на порядок элементов даты, что особенно заметно в длинных временных диапазонах.

Согласованность форматов между слоями

Критически важный аспект — единообразие формата:

  • ось X
  • tooltip
  • legend
  • аннотации

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

Пример согласованного подхода:

const formatDate = format("%d.%m.%Y");

const commonDateFormatter = d => formatDate(new Date(d));

Использование одной функции во всех слоях:

axisBottom={{ format: commonDateFormatter }}

tooltip={({ point }) => (
  <div>{commonDateFormatter(point.data.x)}</div>
)}

Частые ошибки при форматировании дат

  • передача строк без явного парсинга в time scale
  • смешивание UTC и локального времени
  • использование разных форматов в оси и tooltip
  • хранение дат как произвольных строк без стандарта ISO
  • отсутствие контроля точности (precision)

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

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

При увеличении диапазона данных формат легенды и осей должен становиться более абстрактным:

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

Пример адаптивного форматирования:

const adaptiveFormat = value => {
  const date = new Date(value);

  const diff = Date.now() - date.getTime();

  if (diff < 1000 * 60 * 60 * 24) {
    return format("%H:%M")(date);
  }

  if (diff < 1000 * 60 * 60 * 24 * 30) {
    return format("%d.%m")(date);
  }

  return format("%m.%Y")(date);
};

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

Использование кастомных легенд для временных диапазонов

При работе с диапазонами дат (например, недельные серии) легенды могут представлять интервалы:

legends={[
  {
    anchor: "bottom",
    direction: "row",
    itemWidth: 140,
    format: value => {
      const start = new Date(value.start);
      const end = new Date(value.end);

      return `${format("%d.%m")(start)} - ${format("%d.%m")(end)}`;
    }
  }
]}

Такой формат делает легенду частью временной шкалы, а не просто идентификатором серии.

Итоговая роль форматирования дат в Nivo

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