Рендер кастомных компонентов внутри графика

Библиотека Nivo построена вокруг идеи декларативного описания графиков с возможностью точечного расширения через переопределяемые компоненты. Основной принцип заключается в том, что любой визуальный элемент внутри графика — узел, подпись, подсказка, слой, маркер — может быть заменён собственной реализацией, не затрагивая остальную систему рендеринга.

Ключевая особенность архитектуры заключается в том, что график собирается как композиция слоёв и компонентов, каждый из которых отвечает за строго ограниченную часть визуализации. Это позволяет внедрять кастомные элементы без переписывания логики построения данных.


Кастомные слои как основа расширения графика

В большинстве визуализаций Nivo используется концепция слоёв (layers). Каждый слой — это функция или React-компонент, который получает доступ к текущему состоянию графика и отрисовывает часть SVG/Canvas.

Типичный порядок слоёв в линейном графике может выглядеть так:

  • сетка
  • оси
  • линии данных
  • точки
  • аннотации
  • интерактивные элементы

Каждый слой можно заменить или дополнить.

Пример подключения кастомного слоя

<ResponsiveLine
    data={data}
    layers={[
        'grid',
        'markers',
        'axes',
        'lines',
        'points',
        customLayer,
        'legends'
    ]}
/>

Функция кастомного слоя принимает контекст визуализации:

const customLayer = ({ series, xScale, yScale, innerWidth, innerHeight }) => {
    return (
        <g>
            {series.map(s => (
                <circle
                    key={s.id}
                    cx={xScale(s.data[0].x)}
                    cy={yScale(s.data[0].y)}
                    r={6}
                    fill="red"
                />
            ))}
        </g>
    );
};

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


Переопределение элементов данных (nodes, bars, points)

Каждый тип графика предоставляет возможность заменить отрисовку базовых элементов данных.

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

В ResponsiveLine можно переопределить компонент точек через pointComponent.

const CustomPoint = ({ x, y, size, color, data }) => (
    <g transform={`translate(${x},${y})`}>
        <rect
            width={size}
            height={size}
            fill={color}
            transform={`translate(${-size / 2},${-size / 2})`}
        />
    </g>
);

Подключение:

<ResponsiveLine
    data={data}
    pointComponent={CustomPoint}
/>

Такой подход позволяет заменить стандартные круги на любые SVG-примитивы или даже композиции элементов.


Кастомные элементы столбцов

В столбчатых графиках используется barComponent, который управляет отрисовкой каждого столбца.

const CustomBar = ({ x, y, width, height, color, data }) => (
    <g>
        <rect
            x={x}
            y={y}
            width={width}
            height={height}
            fill={color}
            rx={4}
        />
        <text
            x={x + width / 2}
            y={y - 6}
            textAnchor="middle"
            fontSize={10}
        >
            {data.value}
        </text>
    </g>
);

Кастомизация подсказок (tooltip)

Подсказки в Nivo реализованы как React-компоненты, что делает их полностью заменяемыми. Они получают контекст точки или области, над которой находится курсор.

Базовая структура кастомного tooltip

const CustomTooltip = ({ point }) => {
    return (
        <div style={{ padding: 12, background: 'white', border: '1px solid #ccc' }}>
            <strong>{point.data.x}</strong>
            <div>{point.data.y}</div>
        </div>
    );
};

Подключение:

<ResponsiveLine
    data={data}
    tooltip={CustomTooltip}
/>

В более сложных сценариях tooltip может агрегировать данные нескольких серий или выполнять вычисления на лету, например отображать процентное изменение или скользящее среднее.


Работа с кастомными метками и подписями

Подписи в Nivo часто являются частью слоёв или отдельных компонентов внутри элементов данных.

Кастомные labels в pie chart

const CustomLabel = ({ datum, label, percent }) => (
    <g>
        <text
            textAnchor="middle"
            dominantBaseline="central"
            fill="#000"
        >
            {label}
        </text>
        <text
            y={14}
            textAnchor="middle"
            fontSize={10}
            fill="#666"
        >
            {`${Math.round(percent * 100)}%`}
        </text>
    </g>
);

Подключение:

<Pie
    data={data}
    arcLabel={CustomLabel}
/>

Инъекция логики через render-props и функции компонентов

Многие компоненты Nivo допускают передачу функций вместо JSX-компонентов. Это позволяет управлять рендерингом на основе текущего состояния графика.

Например, настройка цвета точки в зависимости от значения:

const getPointColor = point =>
    point.data.y > 100 ? 'red' : 'green';

<ResponsiveLine
    data={data}
    colors={getPointColor}
/>

Такой подход используется для динамической стилизации без необходимости полного переопределения компонента.


Кастомные маркеры и аннотации

Маркерные линии и аннотации реализуются как отдельные слои или компоненты поверх графика.

const Marker = ({ x1, x2, y1, y2 }) => (
    <line
        x1={x1}
        x2={x2}
        y1={y1}
        y2={y2}
        stroke="black"
        strokeDasharray="4 4"
    />
);

Использование позволяет добавлять:

  • целевые значения
  • пороговые зоны
  • визуальные ориентиры

Кастомные темы и стилизация компонентов

Хотя тема не является визуальным компонентом напрямую, она влияет на все кастомные элементы через контекст.

const theme = {
    axis: {
        ticks: {
            text: {
                fontSize: 12,
                fill: '#333'
            }
        }
    },
    grid: {
        line: {
            stroke: '#eee'
        }
    }
};
<ResponsiveLine
    data={data}
    theme={theme}
/>

Кастомные компоненты должны учитывать тему, чтобы сохранять визуальную согласованность.


Использование SVG и гибридных подходов

Основной рендер в Nivo осуществляется через SVG, что делает возможным комбинирование стандартных графических примитивов с кастомной геометрией.

Внутри кастомных компонентов можно использовать:

  • <circle>
  • <rect>
  • <path>
  • <foreignObject> для HTML-вставок

Пример HTML внутри графика:

<foreignObject x={x} y={y} width={100} height={50}>
    <div style={{ fontSize: 10 }}>
        динамический блок
    </div>
</foreignObject>

Контекст данных внутри кастомных компонентов

Каждый кастомный компонент получает строго типизированный набор данных, который зависит от типа графика. Это может включать:

  • координаты в пикселях
  • исходные данные точки
  • масштабы осей
  • размеры внутреннего контейнера
  • активные события (hover, click)

Эта модель позволяет строить полностью управляемые визуальные элементы без обращения к DOM напрямую.


Комбинирование нескольких кастомизаций в одном графике

На практике кастомные компоненты редко используются изолированно. Чаще применяется комбинация:

  • кастомные точки
  • изменённые слои
  • специализированный tooltip
  • аннотации
  • динамическая стилизация
<ResponsiveLine
    data={data}
    pointComponent={CustomPoint}
    tooltip={CustomTooltip}
    layers={[
        'grid',
        'markers',
        customAnnotations,
        'axes',
        'lines',
        'points',
        customOverlay
    ]}
/>

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


Ограничения и особенности интеграции кастомных компонентов

При создании собственных компонентов важно учитывать особенности внутреннего рендеринга:

  • пересчёт координат выполняется библиотекой, а не вручную
  • кастомные элементы должны быть производительными, так как могут вызываться на каждый render
  • при использовании Canvas-версий компоненты работают иначе и чаще заменяются функциями отрисовки, а не React-элементами
  • слои рендерятся в строгом порядке, и нарушение порядка может приводить к перекрытию элементов

Эти особенности определяют архитектурные решения при создании сложных визуализаций на базе Nivo