Визуализация круговых диаграмм в Nivo строится вокруг строго определённой структуры данных. Основной принцип заключается в том, что библиотека не выполняет агрегацию «на лету» (в большинстве случаев), а ожидает уже подготовленный набор категорий с числовыми значениями.
Классическая форма входных данных представляет собой массив объектов:
const data = [
{ id: 'React', value: 45 },
{ id: 'Vue', value: 25 },
{ id: 'Angular', value: 20 },
{ id: 'Svelte', value: 10 }
];
Для корректной работы Pie-компонента критичны два свойства:
{ id: string, value: number }
Поле id используется как ключ сегмента, а
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 }
]
Результат будет идентичным предыдущему примеру, поскольку вычисление происходит относительно суммы.
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) };
});
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>
)}
/>
type PieDatum = {
id: string | number;
value: number;
[key: string]: any;
};
Расширенный вариант:
type ExtendedPieDatum = {
id: string;
value: number;
color?: string;
meta?: {
label?: string;
description?: string;
};
};
{ id: 'A', value: '10' } // строка вместо числа
Последствия:
[
{ 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);
Сортировка влияет на порядок сегментов и читаемость визуализации.
Формат данных напрямую определяет:
Даже минимальные изменения структуры (например, смена
id) приводят к полной перестройке диаграммы, так как каждый
сегмент идентифицируется по ключу.