Предобработка данных перед передачей в Nivo

Библиотека Nivo опирается на строго определённые структуры данных, которые отличаются в зависимости от типа графика. Предобработка данных становится ключевым этапом, поскольку API-ответы, базы данных и аналитические системы почти никогда не возвращают данные в форме, готовой для визуализации.

Для разных компонентов используются разные схемы:

  • Bar chart — массив объектов с числовыми полями и ключом категории
  • Line chart — массив серий, каждая из которых содержит массив точек { x, y }
  • Pie chart — плоский массив объектов с полями id и value
  • Heatmap — матрица или список координат
  • TreeMap — иерархические структуры

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


Нормализация API-ответов

Реальные данные часто приходят в виде вложенных объектов или нестабильных структур:

{
  "result": [
    { "name": "A", "metrics": { "sales": 120, "profit": 30 } },
    { "name": "B", "metrics": { "sales": 90, "profit": 10 } }
  ]
}

Для Nivo Bar Chart требуется преобразование в:

[
  { "name": "A", "sales": 120, "profit": 30 },
  { "name": "B", "sales": 90, "profit": 10 }
]

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

Типовые операции:

  • извлечение вложенных значений (metrics.sales)
  • переименование ключей под требования визуализации
  • удаление служебных полей (id, timestamp, если не используются)

Агрегация и группировка

Часто данные поступают на уровне событий, а не агрегированных значений:

[
  { "category": "A", "value": 10 },
  { "category": "A", "value": 15 },
  { "category": "B", "value": 5 }
]

Для визуализации требуется агрегация:

[
  { "category": "A", "value": 25 },
  { "category": "B", "value": 5 }
]

Реализация сводится к использованию reduce:

const grouped = Object.values(
  data.reduce((acc, item) => {
    if (!acc[item.category]) {
      acc[item.category] = { category: item.category, value: 0 }
    }
    acc[item.category].value += item.value
    return acc
  }, {})
)

В сложных случаях применяется многомерная агрегация по нескольким ключам (например, категория + дата).


Преобразование данных для Bar charts

Bar chart в Nivo требует табличной структуры:

[
  { country: "Kazakhstan", 2023: 120, 2024: 140 },
  { country: "Russia", 2023: 100, 2024: 130 }
]

Ключевой момент — разделение:

  • идентификатор категории (indexBy)
  • набор ключей значений (keys)

Типичная трансформация из “длинного” формата:

const pivot = (data) => {
  const result = {}

  data.forEach(({ country, year, value }) => {
    if (!result[country]) result[country] = { country }
    result[country][year] = value
  })

  return Object.values(result)
}

Line charts и временные ряды

Line chart требует структуры серий:

[
  {
    id: "sales",
    data: [
      { x: "2024-01-01", y: 120 },
      { x: "2024-01-02", y: 90 }
    ]
  }
]

Предобработка временных рядов включает:

Парсинг дат

const parsed = data.map(d => ({
  x: new Date(d.date),
  y: Number(d.value)
}))

Группировка по сериям

const seriesMap = {}

data.forEach(item => {
  if (!seriesMap[item.metric]) {
    seriesMap[item.metric] = { id: item.metric, data: [] }
  }

  seriesMap[item.metric].data.push({
    x: item.date,
    y: item.value
  })
})

Сортировка по времени

series.forEach(s => {
  s.data.sort((a, b) => new Date(a.x) - new Date(b.x))
})

Без сортировки линии могут отображаться с артефактами и “ломаной” геометрией.


Pie/Donut preprocessing

Pie chart требует максимально упрощённую структуру:

[
  { id: "Chrome", value: 60 },
  { id: "Firefox", value: 25 },
  { id: "Safari", value: 15 }
]

Предобработка обычно включает:

  • агрегацию по категориям
  • фильтрацию нулевых значений
  • округление
const pieData = Object.entries(raw).map(([key, value]) => ({
  id: key,
  value: Math.round(value)
}))

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


Stacked charts и подготовка слоёв

Stacked bar/area charts требуют согласованного набора ключей:

[
  { month: "Jan", desktop: 120, mobile: 80 },
  { month: "Feb", desktop: 100, mobile: 90 }
]

Предобработка включает:

  • заполнение отсутствующих ключей нулями
  • унификацию набора категорий
const normalizeKeys = (data, keys) =>
  data.map(item => {
    const normalized = { ...item }
    keys.forEach(k => {
      if (normalized[k] == null) normalized[k] = 0
    })
    return normalized
  })

Очистка данных: null, undefined, выбросы

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

Удаление пустых точек

data.filter(d => d.value != null && !isNaN(d.value))

Замена null на 0

const safe = data.map(d => ({
  ...d,
  value: d.value ?? 0
}))

Ограничение выбросов

const clamp = (v, min, max) => Math.min(max, Math.max(min, v))

Сортировка и стабильность данных

Nivo чувствителен к порядку элементов в некоторых типах графиков (особенно line и area charts).

Рекомендуемые принципы:

  • всегда сортировать временные ряды
  • фиксировать порядок категорий
  • избегать случайной перестановки массивов при ререндере
const stableSort = (data) =>
  [...data].sort((a, b) => a.label.localeCompare(b.label))

Мемоизация и производительность в React

При использовании React повторная предобработка данных на каждом рендере приводит к деградации производительности.

Используется useMemo:

const chartData = useMemo(() => {
  return transform(rawData)
}, [rawData])

Важно учитывать:

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

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

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

Проблема строковых дат

"x": "2024-01-01"

Может интерпретироваться по-разному в разных окружениях.

Решение — нормализация в timestamp

const normalized = data.map(d => ({
  x: new Date(d.x).getTime(),
  y: d.y
}))

Приведение типов и защита от неконсистентных данных

Частая проблема — числовые значения в виде строк:

{ "value": "42" }

Приведение:

const cast = (d) => ({
  ...d,
  value: Number(d.value)
})

Дополнительно:

  • NaN заменяется на 0 или фильтруется
  • булевы значения приводятся явно
  • строки дат валидируются перед парсингом

Типичные ошибки при предобработке

Несовпадение ключей

График ожидает value, а передаётся val или amount.

Нестабильный порядок данных

Особенно критично для линий и областей.

Смешение типов

Числа + строки в одном поле приводят к некорректным масштабам.

Отсутствие унификации структуры

Разные элементы массива имеют разные наборы полей, что ломает оси и легенды.

Избыточная вложенность

Nivo не “разворачивает” объекты автоматически — структура должна быть плоской или строго типизированной.