SliceTooltip и точечные подсказки

В круговых и кольцевых диаграммах Nivo (PieChart, DonutChart) подсказки работают не с отдельной точкой данных, а с группой элементов, объединённых в «срез» (slice). Именно для этого используется концепция SliceTooltip — единый компонент, который отображает агрегированную информацию по активному сектору диаграммы.

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

Основной смысл SliceTooltip заключается в том, чтобы:

  • отображать значение текущего сектора;
  • показывать дополнительные метрики (долю, процент, ключ);
  • формировать единый блок информации для группы данных;
  • обеспечивать единый UX для всех типов pie-based визуализаций.

Механика работы SliceTooltip

При наведении на сектор диаграммы Nivo формирует объект slice, содержащий набор points, относящихся к активному сегменту. Каждый point включает:

  • id категории;
  • значение (value);
  • цвет (color);
  • метаданные серии.

SliceTooltip получает этот объект и рендерит его в пользовательском компоненте.

Типовая структура данных:

{
  id: "react",
  value: 120,
  color: "#61dafb",
  label: "React"
}

В случае составных диаграмм slice может содержать несколько элементов, особенно при stacked-структурах или группировке.


Базовая реализация SliceTooltip

В Nivo кастомизация tooltip осуществляется через свойство tooltip. Для pie-chart оно применяется к срезу:

import { ResponsivePie } from '@nivo/pie'

const MySliceTooltip = ({ slice }) => {
    return (
        <div style={{ padding: 12, background: 'white', border: '1px solid #ccc' }}>
            <strong>{slice.id}</strong>
            <div>Значение: {slice.value}</div>
        </div>
    )
}

const data = [
    { id: 'React', value: 120 },
    { id: 'Vue', value: 80 },
    { id: 'Angular', value: 60 }
]

export default function Chart() {
    return (
        <ResponsivePie
            data={data}
            tooltip={MySliceTooltip}
        />
    )
}

В данном случае tooltip получает объект slice и полностью контролирует отображение информации.


Форматирование данных внутри SliceTooltip

Одной из ключевых задач является преобразование сырых значений в читаемый формат. Часто требуется:

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

Пример вычисления процента:

const MySliceTooltip = ({ slice }) => {
    const total = slice.data.reduce((acc, item) => acc + item.value, 0)
    const percent = ((slice.value / total) * 100).toFixed(2)

    return (
        <div style={{ padding: 10 }}>
            <div>{slice.id}</div>
            <div>{slice.value} ({percent}%)</div>
        </div>
    )
}

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


SliceTooltip и кастомные стили

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

Пример стилизации:

const MySliceTooltip = ({ slice }) => {
    return (
        <div className="tooltip">
            <div className="tooltip__title">{slice.id}</div>
            <div className="tooltip__row">
                <span>Значение:</span>
                <span>{slice.value}</span>
            </div>
        </div>
    )
}

CSS:

.tooltip {
    background: #1e1e1e;
    color: white;
    padding: 12px;
    border-radius: 6px;
    font-size: 12px;
}

.tooltip__title {
    font-weight: 600;
    margin-bottom: 6px;
}

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


Поведение SliceTooltip при взаимодействии

SliceTooltip тесно связан с состоянием active slice. При наведении происходит:

  • вычисление угла сектора;
  • определение активного сегмента;
  • передача данных в tooltip;
  • ререндер компонента tooltip.

Дополнительно можно управлять поведением через:

  • activeOuterRadiusOffset — увеличение активного сектора;
  • arcLinkLabels — подписи;
  • onMouseEnter / onMouseLeave — кастомные обработчики.

Пример:

<ResponsivePie
    data={data}
    activeOuterRadiusOffset={8}
    tooltip={MySliceTooltip}
    onMouseEn ter={(data) => console.log('hover', data.id)}
/>

Точечные подсказки (Point Tooltip)

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

В отличие от SliceTooltip:

  • нет группировки;
  • каждый tooltip относится к одному измерению;
  • структура данных проще и линейнее.

Основная структура point tooltip

Point tooltip получает объект точки:

{
  x: "Jan",
  y: 42,
  serieId: "sales",
  color: "#ff9900"
}

Простейшая реализация:

const MyPointTooltip = ({ point }) => {
    return (
        <div style={{ padding: 10, background: 'white' }}>
            <div>Серия: {point.serieId}</div>
            <div>X: {point.data.x}</div>
            <div>Y: {point.data.y}</div>
        </div>
    )
}

Point Tooltip в LineChart

import { ResponsiveLine } from '@nivo/line'

const data = [
    {
        id: 'sales',
        data: [
            { x: 'Jan', y: 30 },
            { x: 'Feb', y: 45 },
            { x: 'Mar', y: 60 }
        ]
    }
]

export default function Chart() {
    return (
        <ResponsiveLine
            data={data}
            tooltip={MyPointTooltip}
        />
    )
}

Каждая точка графика становится активной зоной, при наведении на которую создаётся tooltip.


Форматирование точек и нормализация данных

В point tooltip часто требуется унификация формата данных:

  • преобразование дат;
  • форматирование чисел;
  • добавление единиц измерения.

Пример:

const MyPointTooltip = ({ point }) => {
    const formatValue = (v) => `${v} ₽`

    return (
        <div>
            <div>{point.data.x}</div>
            <div>{formatValue(point.data.y)}</div>
        </div>
    )
}

Для временных рядов часто применяется преобразование дат:

const date = new Date(point.data.xFormatted)

Поведение tooltip в multi-series графиках

В графиках с несколькими сериями point tooltip может отображать:

  • только активную серию;
  • либо агрегированное состояние по X-координате.

Nivo поддерживает режим crosshair, при котором tooltip синхронизируется между всеми сериями по одной оси X.

Пример:

<ResponsiveLine
    data={data}
    useMesh={true}
    tooltip={MyPointTooltip}
/>

useMesh усиливает точность попадания в точки и улучшает UX при плотных данных.


Сравнение SliceTooltip и Point Tooltip

SliceTooltip и point tooltip различаются по логике взаимодействия:

  • SliceTooltip работает с группой данных (срез);
  • Point tooltip работает с одной точкой;
  • SliceTooltip применяется в pie/donut;
  • Point tooltip применяется в line/bar/scatter;
  • SliceTooltip часто требует агрегации;
  • Point tooltip использует исходные значения напрямую.

Кастомные сценарии объединения логики tooltip

В сложных дашбордах применяется единый подход к визуализации подсказок. Иногда SliceTooltip и point tooltip приводятся к общей модели:

  • единый формат отображения значений;
  • централизованные утилиты форматирования;
  • общий компонент TooltipRenderer.

Пример унифицированного форматтера:

const formatValue = (value, type) => {
    switch (type) {
        case 'currency':
            return `${value} ₽`
        case 'percent':
            return `${value}%`
        default:
            return value
    }
}

Производительность tooltip-компонентов

Tooltip в Nivo рендерится часто, особенно при движении мыши. Для оптимизации используются:

  • мемоизация компонентов (React.memo);
  • вынесение вычислений из render-функции;
  • предварительное вычисление total и derived values;
  • минимизация inline-стилей.

Пример мемоизации:

const MySliceTooltip = React.memo(({ slice }) => {
    return (
        <div>
            {slice.id}: {slice.value}
        </div>
    )
})

Поведение при отсутствии данных

При неполных данных tooltip может получать:

  • null значения;
  • undefined поля;
  • частично заполненные объекты.

В таких случаях требуется защитная логика:

if (!slice) return null
if (slice.value == null) return null

Это предотвращает некорректный рендеринг всплывающих окон.


Стилизация позиционирования tooltip

Nivo автоматически управляет позиционированием tooltip, однако кастомные компоненты могут влиять на:

  • ширину блока;
  • смещение относительно курсора;
  • поведение при краях экрана.

Часто используется фиксированная минимальная ширина:

.tooltip {
    min-width: 120px;
    pointer-events: none;
}

pointer-events: none предотвращает «дрожание» при наведении.