Форматы данных для Pie

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

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

const data = [
  { id: 'React', value: 45 },
  { id: 'Vue', value: 25 },
  { id: 'Angular', value: 20 },
  { id: 'Svelte', value: 10 }
];

Обязательные поля

Для корректной работы Pie-компонента критичны два свойства:

  • id — идентификатор категории (строка)
  • value — числовое значение сегмента
{ id: string, value: number }

Поле id используется как ключ сегмента, а value определяет угол сектора относительно суммы всех значений.

Роль значения value

value трактуется как абсолютная величина, а не процент. Проценты вычисляются внутри Nivo автоматически:

percentage = value / sum(values)

Это означает, что передача процентов напрямую возможна, но не обязательна — библиотека не различает «сырые» и «нормализованные» данные.


Категориальная модель данных

Pie-чарты ориентированы на категориальное представление данных, где каждая запись — это отдельная группа.

Типичная модель доменной агрегации

const data = [
  { id: 'Desktop', value: 120 },
  { id: 'Mobile', value: 300 },
  { id: 'Tablet', value: 80 }
];

В этом случае каждая категория представляет агрегированное значение, полученное заранее (например, из аналитики или SQL-запроса).

Важно: отсутствие вложенности

Nivo Pie не поддерживает вложенные структуры вида:

// ❌ Неподдерживаемый формат
const data = {
  Desktop: { chrome: 50, firefox: 30 }
};

Вместо этого требуется предварительное преобразование в плоский массив:

const data = [
  { id: 'Desktop - Chrome', value: 50 },
  { id: 'Desktop - Firefox', value: 30 }
];

Альтернативные ключи и кастомные маппинги

Хотя стандартные поля — id и value, Nivo допускает переопределение через пропсы компонента:

<Pie
  data={data}
  id="labelKey"
  value="metricKey"
/>

Пример данных:

const data = [
  { labelKey: 'A', metricKey: 10 },
  { labelKey: 'B', metricKey: 20 }
];

Практическое значение кастомных ключей

Использование альтернативных ключей часто возникает при работе с API, где структура фиксирована:

const apiData = [
  { name: 'Email', count: 150 },
  { name: 'Social', count: 90 }
];

Маппинг:

<Pie data={apiData} id="name" value="count" />

Подготовка данных: агрегация и нормализация

Перед передачей данных в Pie часто выполняется предварительная обработка.

Группировка значений

Исходный список событий:

const events = [
  { type: 'click' },
  { type: 'scroll' },
  { type: 'click' },
  { type: 'hover' }
];

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

const grouped = Object.entries(
  events.reduce((acc, item) => {
    acc[item.type] = (acc[item.type] || 0) + 1;
    return acc;
  }, {})
).map(([id, value]) => ({ id, value }));

Результат:

[
  { id: 'click', value: 2 },
  { id: 'scroll', value: 1 },
  { id: 'hover', value: 1 }
]

Работа с нулевыми и пустыми значениями

Нулевые значения

Сегменты с value: 0 могут:

  • отображаться как тонкие линии (зависит от конфигурации)
  • быть полностью скрытыми при фильтрации
const data = [
  { id: 'A', value: 0 },
  { id: 'B', value: 10 }
];

Часто используется предварительная фильтрация:

const filtered = data.filter(d => d.value > 0);

Отсутствующие данные

Отсутствие поля value приводит к некорректной отрисовке. Такие записи должны быть исключены или нормализованы:

const safeData = rawData
  .map(d => ({ ...d, value: d.value ?? 0 }))
  .filter(d => typeof d.value === 'number');

Работа с процентными данными

Несмотря на то что Pie ожидает абсолютные значения, часто приходят уже рассчитанные проценты:

const data = [
  { id: 'A', value: 40 },
  { id: 'B', value: 60 }
];

Даже если сумма равна 100, Nivo всё равно выполняет внутреннюю нормализацию.

Особенность интерпретации

Если сумма не равна 100:

[
  { id: 'A', value: 4 },
  { id: 'B', value: 6 }
]

Результат будет идентичным предыдущему примеру, поскольку вычисление происходит относительно суммы.


Форматы данных из внешних источников

CSV-подобные данные

const csvData = `
A,10
B,20
C,30
`;

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

const data = csvData
  .trim()
  .split('\n')
  .map(row => {
    const [id, value] = row.split(',');
    return { id, value: Number(value) };
  });

API JSON с метаданными

const apiResponse = [
  { category: 'Search', metrics: { count: 120 } },
  { category: 'Ads', metrics: { count: 80 } }
];

Маппинг:

const data = apiResponse.map(item => ({
  id: item.category,
  value: item.metrics.count
}));

Дополнительные поля в объектах данных

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

const data = [
  {
    id: 'React',
    value: 40,
    color: '#61dafb',
    meta: {
      description: 'Frontend library'
    }
  }
];

Использование дополнительных данных

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

<Pie
  data={data}
  tooltip={({ datum }) => (
    <div>
      {datum.id}: {datum.value}
      <br />
      {datum.data.meta?.description}
    </div>
  )}
/>

Типизация структуры данных (TypeScript)

type PieDatum = {
  id: string | number;
  value: number;
  [key: string]: any;
};

Расширенный вариант:

type ExtendedPieDatum = {
  id: string;
  value: number;
  color?: string;
  meta?: {
    label?: string;
    description?: string;
  };
};

Ошибки структуры и их причины

Несовпадение типов value

{ id: 'A', value: '10' } // строка вместо числа

Последствия:

  • некорректная сумма
  • отсутствие сегмента или NaN в вычислениях

Дублирующиеся id

[
  { id: 'A', value: 10 },
  { id: 'A', value: 20 }
]

Поведение зависит от конфигурации, но чаще всего приводит к визуальной неоднозначности. Предварительная агрегация обязательна.


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

В интерфейсах с фильтрацией и переключением периодов данные часто перестраиваются динамически:

function buildPieData(raw, key) {
  return raw.map(item => ({
    id: item[key],
    value: item.value
  }));
}

Пример переключения:

const daily = buildPieData(apiData, 'day');
const monthly = buildPieData(apiData, 'month');

Оптимизация структуры для больших наборов

При большом количестве категорий (50+) структура данных должна учитывать:

  • предварительную агрегацию
  • удаление нулевых значений
  • сортировку по убыванию
const optimized = data
  .filter(d => d.value > 0)
  .sort((a, b) => b.value - a.value);

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


Взаимосвязь данных и визуального результата

Формат данных напрямую определяет:

  • размер сегмента
  • порядок отображения
  • группировку цветов (при использовании схем)
  • поведение tooltip и legends

Даже минимальные изменения структуры (например, смена id) приводят к полной перестройке диаграммы, так как каждый сегмент идентифицируется по ключу.