В визуализациях на базе библиотеки @nivo подсказка (tooltip) является одним из ключевых интерактивных элементов, обеспечивающих контекстное объяснение данных при наведении на элементы графика. Стандартная реализация tooltip покрывает базовые сценарии отображения значений, однако в реальных проектах часто требуется адаптация под предметную область: форматирование чисел, отображение нескольких метрик, добавление визуальных маркеров, интеграция с дизайн-системой.
Кастомный компонент подсказки в Nivo представляет собой
React-компонент, который получает данные точки графика и возвращает
произвольную разметку. Механизм основан на передаче функции или
компонента в свойство tooltip соответствующего графика.
Во всех основных типах графиков Nivo (line, bar, area, scatter) tooltip получает структуру данных, описывающую активную точку или группу точек. Типичный объект содержит:
id — идентификатор серииvalue — числовое значениеcolor — цвет серииindexValue — значение оси Xdata — исходный объект точкиПример структуры:
{
id: 'series A',
value: 124,
color: '#ff4d4f',
indexValue: '2024-01',
data: {
x: '2024-01',
y: 124
}
}
В компоненте Line из @nivo/line tooltip задаётся через проп
tooltip.
import { ResponsiveLine } from '@nivo/line'
const CustomTooltip = ({ point }) => {
return (
<div style={{
background: '#1f1f1f',
padding: '10px 12px',
borderRadius: 6,
color: '#fff',
fontSize: 12
}}>
<div><strong>{point.seriesId}</strong></div>
<div>Значение: {point.data.y}</div>
<div>Категория: {point.data.x}</div>
</div>
)
}
const Chart = ({ data }) => (
<ResponsiveLine
data={data}
margin={{ top: 40, right: 20, bottom: 40, left: 40 }}
xScale={{ type: 'point' }}
yScale={{ type: 'linear', min: 'auto', max: 'auto' }}
axisBottom={{ legend: 'время' }}
axisLeft={{ legend: 'значение' }}
tooltip={CustomTooltip}
/>
)
Ключевой момент заключается в том, что tooltip получает уже нормализованные данные, поэтому не требуется ручная агрегация.
В реальных интерфейсах числовые значения редко отображаются «как
есть». Часто используется Intl.NumberFormat:
const formatValue = (value) =>
new Intl.NumberFormat('ru-RU', {
style: 'decimal',
maximumFractionDigits: 2
}).format(value)
Интеграция в tooltip:
const CustomTooltip = ({ point }) => {
return (
<div className="tooltip">
<div>{point.seriesId}</div>
<div>{formatValue(point.data.y)}</div>
</div>
)
}
Такой подход обеспечивает единообразие представления данных во всех графиках системы.
В групповых графиках (stacked, grouped bar) tooltip может содержать массив точек. Например, в @nivo/bar компоненте данные часто приходят как набор значений по одному индексу.
const GroupedTooltip = ({ data }) => {
return (
<div style={{ background: '#222', padding: 12 }}>
<div>Категория: {data.indexValue}</div>
{data.data.map((item) => (
<div key={item.id} style={{ color: item.color }}>
{item.id}: {item.value}
</div>
))}
</div>
)
}
Здесь data.data содержит все серии для одного значения
оси X, что позволяет строить сравнительные подсказки.
Кастомный tooltip в Nivo не ограничен текстом. Возможна интеграция цветовых индикаторов, иконок, полос прогресса и других элементов интерфейса.
const EnhancedTooltip = ({ point }) => {
return (
<div style={{
background: '#fff',
border: `1px solid ${point.color}`,
padding: 10,
borderRadius: 8,
minWidth: 160
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{
width: 10,
height: 10,
background: point.color,
display: 'inline-block'
}} />
<strong>{point.seriesId}</strong>
</div>
<div style={{ marginTop: 6 }}>
Значение: {point.data.y}
</div>
</div>
)
}
Цвет серии используется как визуальный якорь, связывающий tooltip с линией или столбцом на графике.
В @nivo/scatterplot tooltip часто применяется для отображения многомерных данных.
const ScatterTooltip = ({ node }) => {
return (
<div style={{ background: '#111', color: '#fff', padding: 10 }}>
<div>ID: {node.data.id}</div>
<div>X: {node.x}</div>
<div>Y: {node.y}</div>
<div>Группа: {node.serieId}</div>
</div>
)
}
Особенность scatter-графиков заключается в том, что tooltip часто становится основным способом интерпретации точек, поскольку визуально они могут быть плотными и перекрывающимися.
В Nivo tooltip-компоненты могут получать не только отображаемые значения, но и дополнительные поля из исходного dataset.
const CustomTooltip = ({ point }) => {
const meta = point.data.metadata
return (
<div>
<div>{point.data.x}</div>
<div>{point.data.y}</div>
{meta && (
<div>
Регион: {meta.region}
</div>
)}
</div>
)
}
Такой подход используется для бизнес-логики, где точка содержит расширенную информацию: идентификаторы, категории, статусные флаги.
Tooltip в Nivo можно полностью контролировать:
enableSlicesПример управления поведением:
<ResponsiveLine
data={data}
enableSlices="x"
tooltip={CustomTooltip}
sliceTooltip={GroupedTooltip}
/>
При использовании enableSlices="x" tooltip агрегирует
все точки по оси X, что полезно для временных рядов.
При работе с большими графиками tooltip становится частью высокочастотных ререндеров. Каждое движение курсора может инициировать обновление состояния.
Оптимизация достигается через:
React.memo)const MemoTooltip = React.memo(({ point }) => {
return (
<div>
{point.seriesId}: {point.data.y}
</div>
)
})
Дополнительная оптимизация — предварительное форматирование данных до передачи в график.
Вместо inline-стилей часто используется интеграция с CSS или CSS-in-JS:
import styled from 'styled-components'
const TooltipBox = styled.div`
background: #0f172a;
color: white;
padding: 12px;
border-radius: 8px;
font-size: 12px;
`
const CustomTooltip = ({ point }) => (
<TooltipBox>
<div>{point.seriesId}</div>
<div>{point.data.y}</div>
</TooltipBox>
)
Такой подход обеспечивает консистентность интерфейса при масштабировании визуализаций.
Разные графики требуют разных стратегий отображения:
Унификация tooltip-компонентов достигается через создание базового слоя:
const BaseTooltip = ({ title, items }) => (
<div className="tooltip">
<div>{title}</div>
{items.map((i) => (
<div key={i.label}>
{i.label}: {i.value}
</div>
))}
</div>
)
В реальных данных tooltip должен учитывать:
const SafeTooltip = ({ point }) => {
if (!point || !point.data) return null
return (
<div>
{point.seriesId ?? 'unknown'}
</div>
)
}
Такая защита предотвращает ошибки при динамической подгрузке данных или частичном рендере графика.