Кастомные слои (layers): добавление и переопределение

Визуализация в 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 уже определён стандартный набор слоёв. Например, в линейном графике можно встретить:

  • сетку координат
  • оси
  • линии данных
  • точки (points)
  • подсказки и взаимодействия (crosshair, mesh)
  • аннотации

Порядок слоёв критически важен. Например, сетка должна находиться ниже линий данных, а интерактивные элементы — выше всех визуальных компонентов.

Типичный порядок:

  1. Grid (сетка)
  2. Axes (оси)
  3. Areas / Lines (данные)
  4. Points (узлы)
  5. Markers / Annotations
  6. Mesh / Interactivity

Этот порядок может изменяться через свойство layers, которое поддерживается большинством компонентов Nivo.

Переопределение слоёв через layers

Механизм 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
    ]}
/>

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

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

Слои получают уже преобразованные данные, адаптированные под визуализацию. В зависимости от типа графика структура отличается:

  • в Line — массив серий с точками
  • в Bar — категории и значения
  • в Pie — сегменты с углами

Пример работы с точками в линейном графике:

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
  • отказ от тяжёлых операций внутри render-функции слоя

При больших наборах данных предпочтение часто отдаётся Canvas-рендеру Nivo, где слои трансформируются в последовательность отрисовки на canvas-контексте.

Слои в различных типах графиков

Поведение системы слоёв сохраняется, но набор доступных данных отличается.

В столбчатых графиках (Bar) слои работают с:

  • индексами категорий
  • значениями столбцов
  • группировкой series

В круговых графиках (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
                });
            }}
        />
    );
};

Такие слои часто размещаются поверх всех визуальных элементов, обеспечивая контроль взаимодействий без вмешательства в базовую логику библиотеки.