Встроенная система подсказок

Архитектура tooltip-механизма

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

Подсказки в Nivo реализуются через декларативные свойства компонентов, где поведение tooltip задаётся через пропсы уровня графика, а не через императивное управление DOM. Это обеспечивает единообразие интерфейса между различными типами визуализаций.

Ключевым принципом является разделение ответственности:

  • график отвечает за вычисление активного элемента;
  • tooltip отвечает за визуализацию контекста;
  • пользовательская логика может переопределять оба уровня через кастомные функции.

Базовый механизм отображения подсказок

Во всех компонентах семейства Nivo (ResponsiveBar, ResponsiveLine, ResponsivePie и других) подсказка активируется при наведении на элемент данных. В момент hover библиотека формирует объект данных, содержащий:

  • идентификатор серии
  • значение (value)
  • индекс элемента
  • цвет (color)
  • дополнительные метаданные (payload)
  • координаты позиции (x, y или x/y domain values)

Этот объект передаётся в tooltip renderer.

Типовая конфигурация:

<ResponsiveBar
    data={data}
    keys={['sales']}
    indexBy="month"
    tooltip={({ id, value, color }) => (
        <div style={{ padding: 8, background: 'white' }}>
            <strong style={{ color }}>{id}</strong>: {value}
        </div>
    )}
/>

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


Типизация и контекст tooltip

Внутренне tooltip в Nivo работает как функция:

tooltip: (node: TooltipProps) => ReactNode

Где TooltipProps может включать:

  • datum — объект данных
  • color — цвет элемента
  • value — числовое значение
  • series — серия данных (в линейных графиках)
  • point — точка (в scatter/line)
  • x/y координаты
  • index — позиция элемента

Это позволяет строить tooltip не только как текстовую подсказку, но как полноценный UI-компонент с логикой.


Подсказки в столбчатых графиках

В ResponsiveBar tooltip связан с отдельным баром. При наведении библиотека определяет активный прямоугольник и передаёт его контекст.

Расширенная настройка:

<ResponsiveBar
    data={data}
    keys={['sales', 'profit']}
    indexBy="month"
    tooltip={({ id, value, color, indexValue }) => (
        <div>
            <div>{indexValue}</div>
            <div style={{ color }}>{id}</div>
            <div>{value}</div>
        </div>
    )}
/>

Особенность bar tooltip заключается в наличии indexValue, который отражает категорию оси X (например, месяц). Это делает подсказку многомерной: одновременно отображается категория и серия.

В stacked bar charts tooltip получает дополнительные данные о сегменте стека, что позволяет различать вклад каждого слоя.


Подсказки в линейных графиках

В ResponsiveLine система подсказок работает на уровне точек (points). Каждая точка представляет собой вычисленное значение линии.

<ResponsiveLine
    data={data}
    tooltip={({ point }) => (
        <div>
            <div>{point.data.xFormatted}</div>
            <div>{point.serieId}</div>
            <div>{point.data.yFormatted}</div>
        </div>
    )}
/>

В отличие от bar charts, здесь tooltip оперирует:

  • серией (serieId)
  • координатами (x/y)
  • форматированными значениями (xFormatted, yFormatted)

Nivo заранее вычисляет форматированные версии, чтобы избежать повторного форматирования при каждом рендере tooltip.


Slice tooltips в линейных графиках

Отдельный механизм — slice tooltips, активируемый через useMesh или enableSlices.

Slice представляет вертикальный или горизонтальный срез всех линий в конкретной координате X.

<ResponsiveLine
    data={data}
    useMesh={true}
    sliceTooltip={({ slice }) => (
        <div>
            <strong>{slice.points[0].data.xFormatted}</strong>
            {slice.points.map(point => (
                <div key={point.id}>
                    {point.serieId}: {point.data.yFormatted}
                </div>
            ))}
        </div>
    )}
/>

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

Система slice-tooltip формирует массив slice.points, каждый элемент которого содержит точку из соответствующей серии.


Подсказки в круговых диаграммах

В ResponsivePie tooltip привязан к сегменту дуги. Каждый сектор передаёт данные:

  • id
  • label
  • value
  • percentage
  • color
<ResponsivePie
    data={data}
    tooltip={({ datum }) => (
        <div>
            <div style={{ color: datum.color }}>{datum.label}</div>
            <div>{datum.value}</div>
            <div>{datum.formattedValue}</div>
        </div>
    )}
/>

Дополнительно Pie charts часто используют центрированные tooltip-эффекты через arcLinkLabels или внешние overlay-компоненты, но базовая система остаётся той же: активный сегмент → datum → tooltip renderer.


Полностью кастомные tooltip-компоненты

Nivo допускает передачу полноценного React-компонента вместо функции:

const CustomTooltip = ({ id, value, color }) => {
    return (
        <div style={{
            padding: 10,
            border: `1px solid ${color}`,
            background: '#fff'
        }}>
            <div>{id}</div>
            <div>{value}</div>
        </div>
    );
};

<ResponsiveBar
    data={data}
    keys={['value']}
    indexBy="label"
    tooltip={CustomTooltip}
/>

Такой подход позволяет:

  • использовать хуки React внутри tooltip-компонента
  • подключать состояние (state)
  • использовать контекст приложения
  • внедрять форматирование через внешние утилиты

Tooltip становится полноценной частью React tree, а не просто функцией отображения.


Форматирование значений внутри tooltip

Nivo предоставляет несколько уровней форматирования:

  1. Форматирование на уровне данных (valueFormat)
  2. Форматирование через axis (axisLeft.format)
  3. Форматирование внутри tooltip
<ResponsiveBar
    data={data}
    keys={['value']}
    valueFormat={value => `${value.toFixed(2)} ₽`}
    tooltip={({ value }) => (
        <div>{value.toLocaleString()}</div>
    )}
/>

Часто используется разделение:

  • value — сырое значение
  • formattedValue — подготовленное значение

Это позволяет избежать дублирования логики форматирования между осью и tooltip.


Темизация tooltip

Встроенная система темы Nivo позволяет управлять стилем tooltip глобально:

theme={{
    tooltip: {
        container: {
            background: '#1e1e1e',
            color: '#fff',
            fontSize: 12,
            borderRadius: 4
        }
    }
}}

Tooltip получает стили из темы, если не переопределён вручную.

Приоритет применения стилей:

  1. inline style в tooltip компоненте
  2. кастомный tooltip renderer
  3. theme.tooltip.container
  4. дефолтные стили Nivo

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


Поведение tooltip и оптимизация

Tooltip в Nivo оптимизирован под минимальные перерисовки. Основные механизмы:

  • мемоизация активного элемента
  • отсутствие повторного расчёта данных при hover
  • использование SVG pointer-events вместо DOM listeners
  • выделение overlay-слоя для tooltip

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

Особенно это заметно в:

  • line charts с mesh
  • scatter plots
  • time series визуализациях

Управление видимостью tooltip

Tooltip можно отключать или ограничивать:

<ResponsiveBar
    data={data}
    keys={['value']}
    enableLabel={false}
    tooltip={() => null}
/>

Также можно использовать условное отображение:

tooltip={({ value }) => value > 100 ? <div>{value}</div> : null}

Это позволяет строить контекстные подсказки, зависящие от бизнес-логики.


Взаимодействие tooltip с слоями графика

Nivo использует систему layers, где tooltip фактически является отдельным overlay-слоем. Это означает:

  • tooltip не влияет на геометрию графика
  • tooltip рисуется поверх всех SVG элементов
  • tooltip получает координаты через вычисленные позиции nodes

В bar charts это прямоугольники, в line charts — точки или mesh, в pie charts — arcs.


Частые особенности поведения

  • Tooltip может “прыгать” при быстром движении мыши, если не используется mesh слой
  • В line charts без useMesh hover зависит от точности попадания в точку
  • В stacked bar charts tooltip может объединять несколько значений одного index
  • В pie charts tooltip всегда привязан к активному arc, а не координате

Эти особенности связаны с тем, что tooltip не является самостоятельной сущностью, а отражает состояние интерактивного слоя графика.


Согласованность tooltip между типами графиков

Несмотря на различия визуализаций, Nivo поддерживает унифицированный подход:

  • datum как базовая единица
  • color как обязательный визуальный идентификатор
  • id/label как семантические ключи
  • value как числовая метрика

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