Визуализация в Nivo построена на принципе композиции: график собирается из набора независимых слоёв, каждый из которых отвечает за определённую часть отрисовки. Такой подход используется во всех основных компонентах библиотеки — линейных, столбчатых, круговых и комбинированных графиках.
Слой в Nivo представляет собой либо встроенную сущность, либо пользовательскую функцию, возвращающую JSX-элементы. Порядок слоёв определяет порядок наложения графических элементов в SVG-контейнере: то, что объявлено позже, отображается поверх предыдущих элементов.
Каждый слой получает стандартный набор параметров: данные, масштабные функции, размеры области отрисовки, а также вспомогательные утилиты (цветовые схемы, доступ к осям, состоянию взаимодействия).
Рендеринг графиков в Nivo основан на декларативной модели React. Каждый слой — это функция, которая вызывается в процессе построения графика и возвращает набор SVG- или Canvas-элементов.
Типовая сигнатура пользовательского слоя:
const CustomLayer = (props) => {
const {
data,
xScale,
yScale,
innerWidth,
innerHeight,
margin,
colors
} = props;
return (
<g>
{/* SVG элементы */}
</g>
);
};
Важным аспектом является то, что слой не управляет состоянием графика. Он получает полностью подготовленные данные и отвечает исключительно за их визуальное представление.
Рендеринг слоёв происходит последовательно. Каждый слой накладывается поверх предыдущего, формируя итоговое изображение. Это позволяет внедрять пользовательские элементы без изменения базовой логики библиотеки.
В большинстве компонентов Nivo уже определён стандартный набор слоёв. Например, в линейном графике можно встретить:
Порядок слоёв критически важен. Например, сетка должна находиться ниже линий данных, а интерактивные элементы — выше всех визуальных компонентов.
Типичный порядок:
Этот порядок может изменяться через свойство layers,
которое поддерживается большинством компонентов Nivo.
Механизм layers позволяет полностью заменить стандартный
набор визуальных компонентов или расширить его.
В компоненте линейного графика используется следующая конфигурация:
<ResponsiveLine
data={data}
layers={[
'grid',
'markers',
'axes',
'lines',
'points',
'customLayer'
]}
/>
Каждый элемент массива может быть:
При передаче функции происходит полное включение кастомного рендера в цепочку отрисовки.
Пользовательский слой используется для добавления элементов, отсутствующих в стандартной реализации: дополнительные линии, выделения областей, водяные знаки, кастомные подписи.
Пример слоя, добавляющего вертикальную линию на фиксированной позиции:
const VerticalMarkerLayer = ({ innerHeight, xScale }) => {
const x = xScale(50);
return (
<g>
<line
x1={x}
x2={x}
y1={0}
y2={innerHeight}
stroke="red"
strokeDasharray="4 4"
/>
</g>
);
};
Подключение слоя:
<ResponsiveLine
data={data}
layers={[
'grid',
'markers',
'axes',
'lines',
'points',
VerticalMarkerLayer
]}
/>
Такой слой получает доступ ко всем вычисленным масштабам, что позволяет точно позиционировать элементы в координатной системе графика.
Слои получают уже преобразованные данные, адаптированные под визуализацию. В зависимости от типа графика структура отличается:
Пример работы с точками в линейном графике:
const HighlightMaxPointLayer = ({ series, yScale }) => {
let maxPoint = null;
series.forEach(serie => {
serie.data.forEach(point => {
if (!maxPoint || point.y > maxPoint.y) {
maxPoint = point;
}
});
});
const y = yScale(maxPoint.y);
return (
<circle
cx={maxPoint.x}
cy={y}
r={6}
fill="orange"
/>
);
};
Такой подход позволяет выполнять вычисления непосредственно в слое без изменения исходных данных.
Масштабы xScale и yScale являются ключевыми
инструментами позиционирования. Они преобразуют значения данных в
пиксельные координаты SVG-контейнера.
Для линейного графика:
xScale(value) — позиция по горизонталиyScale(value) — позиция по вертикалиПри этом важно учитывать инверсию оси Y в SVG: рост значений по оси Y визуально направлен вниз, поэтому Nivo уже инкапсулирует корректное преобразование.
Дополнительно доступны параметры:
innerWidth, innerHeight — размеры области
графика без полейmargin — отступы контейнераЭти параметры позволяют точно размещать элементы внутри графика без пересчёта внешних координат.
Несколько пользовательских слоёв могут использоваться одновременно.
Их порядок определяется массивом layers.
const BackgroundLayer = ({ innerWidth, innerHeight }) => (
<rect width={innerWidth} height={innerHeight} fill="#f5f5f5" />
);
const DiagonalLineLayer = ({ innerWidth, innerHeight }) => (
<line
x1={0}
y1={0}
x2={innerWidth}
y2={innerHeight}
stroke="#ccc"
/>
);
Подключение:
layers={[
BackgroundLayer,
'grid',
DiagonalLineLayer,
'axes',
'lines'
]}
В таком случае слои формируют визуальный стек, где каждый следующий слой накладывается поверх предыдущего.
Иногда требуется не замена, а расширение поведения встроенного слоя. Для этого используется обёртка над оригинальным компонентом.
const EnhancedPointsLayer = (props) => {
const baseLayer = props.points;
return (
<g>
{baseLayer}
{baseLayer.map(point => (
<text
key={point.id}
x={point.x}
y={point.y - 10}
textAnchor="middle"
fontSize={10}
>
{point.data.y}
</text>
))}
</g>
);
};
Такой подход позволяет сохранить стандартную отрисовку и дополнить её дополнительной графикой.
Каждый слой является частью React-дерева. Избыточное количество кастомных слоёв увеличивает нагрузку на виртуальный DOM и может влиять на частоту перерисовки.
Оптимизация обычно сводится к следующим принципам:
useMemoПри больших наборах данных предпочтение часто отдаётся Canvas-рендеру Nivo, где слои трансформируются в последовательность отрисовки на canvas-контексте.
Поведение системы слоёв сохраняется, но набор доступных данных отличается.
В столбчатых графиках (Bar) слои работают с:
В круговых графиках (Pie) слои получают:
В линейных графиках (Line) основная работа ведётся с
координатными точками, что делает кастомные слои наиболее гибкими именно
в этом типе визуализации.
Удаление встроенных слоёв выполняется путём исключения их имени из
массива layers.
layers={[
'axes',
'lines',
'points'
]}
В таком случае, например, сетка или интерактивный mesh не будут отрисованы. Это позволяет создавать максимально «чистые» визуализации без лишних элементов интерфейса.
Добавление пользовательского слоя в середину списка влияет на визуальную иерархию без изменения базовой структуры компонента.
Слои могут использоваться не только для статической графики, но и для обработки событий. Например, добавление области hover:
const HoverOverlayLayer = ({ innerWidth, innerHeight, onHover }) => {
return (
<rect
width={innerWidth}
height={innerHeight}
fill="transparent"
onMouseM ove={(event) => {
const bounds = event.target.getBoundingClientRect();
onHover({
x: event.clientX - bounds.left,
y: event.clientY - bounds.top
});
}}
/>
);
};
Такие слои часто размещаются поверх всех визуальных элементов, обеспечивая контроль взаимодействий без вмешательства в базовую логику библиотеки.