Нормализация и трансформация данных

Библиотека ориентирована на декларативную модель: графики описываются через конфигурационные объекты, а не через императивное построение. Центральное ограничение заключается в том, что каждый тип графика ожидает строго определённую форму данных. Это означает, что основная сложность интеграции лежит не в рендеринге, а в подготовке данных.

Типовой поток данных перед визуализацией:

сырой источник → очистка → нормализация → агрегация → трансформация под тип графика → передача в компонент

Сырые данные почти никогда не совпадают с требуемой структурой:

  • API возвращают вложенные JSON-объекты
  • аналитические данные приходят в «широком» формате (wide format)
  • события хранятся как списки логов
  • временные ряды содержат разреженные интервалы
  • категориальные данные требуют группировки

Принципы нормализации данных

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

Ключевые принципы:

1. Единая ось идентификации Любая сущность должна иметь стабильный ключ:

{
  id: "product_a",
  value: 120
}

или для временных рядов:

{
  x: "2026-01-01",
  y: 42
}

2. Плоская структура предпочтительнее вложенной Большинство графиков работают эффективнее с flat-структурами.

3. Явное разделение измерений Категории, значения и временные метки должны быть строго отделены.


Трансформация под линейные графики

Линейные графики в Nivo используют формат временных рядов:

[
  {
    id: "sales",
    data: [
      { x: "2026-01-01", y: 120 },
      { x: "2026-01-02", y: 98 },
      { x: "2026-01-03", y: 140 }
    ]
  }
]

Преобразование из API формата

Исходный формат:

[
  { date: "2026-01-01", metric: "sales", value: 120 },
  { date: "2026-01-01", metric: "profit", value: 30 },
  { date: "2026-01-02", metric: "sales", value: 98 }
]

Нормализация:

const groupByMetric = (data) => {
  const map = new Map();

  data.forEach(({ date, metric, value }) => {
    if (!map.has(metric)) map.set(metric, []);

    map.get(metric).push({
      x: date,
      y: value
    });
  });

  return Array.from(map.entries()).map(([id, data]) => ({
    id,
    data: data.sort((a, b) => new Date(a.x) - new Date(b.x))
  }));
};

Важный аспект трансформации временных рядов

  • сортировка по времени обязательна
  • пропуски могут интерполироваться или оставаться пустыми
  • тип x должен быть согласован (строка или Date)

Нормализация данных для столбчатых диаграмм

Bar charts требуют категориальной структуры:

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

Но Nivo ожидает:

[
  {
    category: "A",
    sales: 10,
    profit: 4
  },
  {
    category: "B",
    sales: 20,
    profit: 7
  }
]

Pivot-трансформация (разворот данных)

Исходные данные:

[
  { category: "A", metric: "sales", value: 10 },
  { category: "A", metric: "profit", value: 4 },
  { category: "B", metric: "sales", value: 20 }
]

Преобразование:

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

  data.forEach(({ category, metric, value }) => {
    if (!result[category]) result[category] = { category };
    result[category][metric] = value;
  });

  return Object.values(result);
};

Особенности нормализации для bar chart

  • отсутствующие метрики должны быть заполнены (0 или null)
  • порядок категорий должен быть фиксирован
  • ключ категории должен быть строковым

Подготовка данных для круговых диаграмм

Pie charts требуют другой модели:

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

Агрегация из логов

Исходные события:

[
  { browser: "Chrome" },
  { browser: "Chrome" },
  { browser: "Firefox" }
]

Трансформация:

const toPieData = (logs) => {
  const count = logs.reduce((acc, item) => {
    acc[item.browser] = (acc[item.browser] || 0) + 1;
    return acc;
  }, {});

  return Object.entries(count).map(([id, value]) => ({
    id,
    value
  }));
};

Нормализация значений

В некоторых случаях требуется процентное представление:

const toPercentPie = (data) => {
  const total = data.reduce((sum, d) => sum + d.value, 0);

  return data.map(d => ({
    ...d,
    value: (d.value / total) * 100
  }));
};

Деревья и иерархическая трансформация

Иерархические структуры используются в treemap и sunburst.

Формат:

{
  id: "root",
  children: [
    {
      id: "group A",
      children: [
        { id: "item 1", value: 10 }
      ]
    }
  ]
}

Построение дерева из плоского списка

Исходные данные:

[
  { path: "A/B/X", value: 10 },
  { path: "A/B/Y", value: 20 }
]

Трансформация:

const buildTree = (data) => {
  const root = { id: "root", children: [] };

  const ins ert = (parts, node, val ue) => {
    const [head, ...rest] = parts;

    let child = node.children.find(c => c.id === head);

    if (!child) {
      child = { id: head, children: [] };
      node.children.push(child);
    }

    if (rest.length === 0) {
      child.value = value;
    } else {
      ins ert(rest, child, val ue);
    }
  };

  data.forEach(({ path, value }) => {
    ins ert(path.split("/"), root, val ue);
  });

  return root;
};

Важные ограничения иерархий

  • листья должны содержать value
  • промежуточные узлы могут содержать агрегированное значение
  • структура должна быть строго древовидной (без циклов)

Нормализация временных рядов с разреженными данными

Реальные данные часто содержат пропуски:

[
  { x: "2026-01-01", y: 10 },
  { x: "2026-01-03", y: 20 }
]

Для корректной визуализации требуется заполнение:

const fillGaps = (data, allDates) => {
  const map = new Map(data.map(d => [d.x, d.y]));

  return allDates.map(date => ({
    x: date,
    y: map.get(date) ?? 0
  }));
};

Унификация форматов перед передачей в Nivo

Разные графики требуют разных контрактов, но общий слой подготовки данных может быть унифицирован:

const normalize = {
  line: (raw) => groupByMetric(raw),
  bar: (raw) => pivot(raw),
  pie: (raw) => toPieData(raw),
  tree: (raw) => buildTree(raw)
};

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


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

Несоответствие ключей

// ошибка: x/y отсутствуют
{ date: "...", value: 10 }

Несортированные временные ряды

График может выглядеть «ломаным» при отсутствии сортировки.

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

x: 1
x: "1"

приводит к некорректной шкале.

Неполная агрегация

Дубликаты категорий без предварительного grouping искажают визуализацию.


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

При больших объёмах данных ключевыми становятся:

  • предварительная агрегация на сервере
  • мемоизация трансформаций
  • использование Map вместо объектов для O(1) доступа
  • потоковая обработка при необходимости
const memoizedTransform = (() => {
  const cache = new Map();

  return (data) => {
    const key = JSON.stringify(data);
    if (cache.has(key)) return cache.get(key);

    const result = pivot(data);
    cache.set(key, result);
    return result;
  };
})();

Согласование данных с масштабами осей

Перед визуализацией важно учитывать диапазоны значений:

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

Подготовка смешанных наборов данных

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

[
  {
    id: "revenue",
    data: [...]
  },
  {
    id: "target",
    data: [...]
  }
]

Требуется синхронизация:

  • одинаковая ось x
  • согласованные диапазоны y
  • единая временная база

Обобщённая модель трансформации

Любая подготовка данных к визуализации сводится к последовательности:

  1. Извлечение сырого набора
  2. Очистка (null, undefined, NaN)
  3. Группировка по ключам
  4. Агрегация значений
  5. Переупаковка в формат конкретного графика
  6. Сортировка и нормализация осей
  7. Финальная валидация структуры

Эта цепочка остаётся общей независимо от типа графика и источника данных