Библиотека ориентирована на декларативную модель: графики описываются через конфигурационные объекты, а не через императивное построение. Центральное ограничение заключается в том, что каждый тип графика ожидает строго определённую форму данных. Это означает, что основная сложность интеграции лежит не в рендеринге, а в подготовке данных.
Типовой поток данных перед визуализацией:
сырой источник → очистка → нормализация → агрегация → трансформация под тип графика → передача в компонент
Сырые данные почти никогда не совпадают с требуемой структурой:
Нормализация данных в контексте визуализации означает приведение разнородных структур к предсказуемому виду, пригодному для рендеринга.
Ключевые принципы:
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 }
]
}
]
Исходный формат:
[
{ 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
}
]
Исходные данные:
[
{ 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);
};
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
}));
};
Разные графики требуют разных контрактов, но общий слой подготовки данных может быть унифицирован:
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 искажают визуализацию.
При больших объёмах данных ключевыми становятся:
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;
};
})();
Перед визуализацией важно учитывать диапазоны значений:
В сложных интерфейсах часто используются комбинированные графики:
[
{
id: "revenue",
data: [...]
},
{
id: "target",
data: [...]
}
]
Требуется синхронизация:
Любая подготовка данных к визуализации сводится к последовательности:
Эта цепочка остаётся общей независимо от типа графика и источника данных