Интерактивные возможности линейных графиков в Nivo строятся вокруг
событий наведения, управления состоянием серии и динамического изменения
визуального веса элементов. Компонент ResponsiveLine
предоставляет несколько уровней взаимодействия: от базового tooltip при
hover до сложного управления активными линиями через легенды, срезы и
кастомные обработчики событий.
При наведении на точки графика Nivo автоматически активирует ближайший элемент данных. В режиме по умолчанию каждая точка получает визуальный акцент: увеличивается радиус, меняется заливка или обводка, а соседние элементы визуально остаются без изменений.
Ключевым механизмом выступает система событий:
onMouseEnteronMouseMoveonMouseLeaveЭти обработчики позволяют синхронизировать внешний UI с состоянием графика, например, отображать дополнительную информацию вне canvas-области.
Пример базовой конфигурации:
<ResponsiveLine
data={data}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ type: 'point' }}
yScale={{ type: 'linear', min: 'auto', max: 'auto' }}
enablePoints={true}
pointSize={8}
useMesh={true}
onMouseEn ter={(point) => console.log('enter', point)}
onMouseLe ave={() => console.log('leave')}
/>
useMesh: true значительно расширяет область захвата
событий, позволяя получать более стабильный hover даже при плотных
данных.
Один из наиболее эффективных способов выделения данных — изменение
визуального приоритета линий через прозрачность. В Nivo это достигается
комбинацией кастомного layer и контроля текущей активной
серии.
Основной подход заключается в хранении состояния активной линии:
const [activeSerie, setActiveSerie] = useState(null);
Далее каждая линия рендерится с условной прозрачностью:
const customLayer = ({ series, lineGenerator }) => {
return series.map(serie => (
<path
key={serie.id}
d={lineGenerator(serie.data)}
fill="none"
stroke={serie.color}
strokeWidth={3}
opacity={!activeSerie || activeSerie === serie.id ? 1 : 0.15}
onMouseEn ter={() => setActiveSerie(serie.id)}
onMouseLe ave={() => setActiveSerie(null)}
/>
));
};
Такой подход создаёт эффект «фокусировки»: одна линия становится основной, остальные уходят в фон без удаления из визуального пространства.
Легенда в Nivo — не только информационный элемент, но и полноценный
интерактивный контроллер. Через legends можно реализовать
поведение, при котором пользователь выделяет конкретную серию.
legends={[
{
anchor: 'right',
direction: 'column',
itemWidth: 100,
itemHeight: 20,
onClick: (datum) => {
setActiveSerie(prev => prev === datum.id ? null : datum.id);
},
effects: [
{
on: 'hover',
style: {
itemOpacity: 1
}
}
]
}
]}
Клик по элементу легенды позволяет переключать фокус между линиями. Это особенно эффективно при большом количестве серий, где одновременное отображение всех линий снижает читаемость.
Механизм enableSlices позволяет группировать данные по
оси X или Y и отображать их как единый интерактивный блок.
<ResponsiveLine
data={data}
enableSlices="x"
sliceTooltip={({ slice }) => (
<div>
{slice.points.map(point => (
<div key={point.id}>
{point.serieId}: {point.data.y}
</div>
))}
</div>
)}
/>
Срезы обеспечивают синхронное выделение всех линий в одной точке X, что особенно полезно при анализе временных рядов.
Поведение срезов:
x — группировка по вертикальному срезуy — группировка по горизонтальному срезуfalse — отключеноTooltip в Nivo полностью заменяем. Стандартный tooltip можно
переопределить через tooltip prop, получая доступ к точке
данных и серии.
tooltip={({ point }) => (
<div style={{ background: 'white', padding: 12, border: '1px solid #ccc' }}>
<strong>{point.serieId}</strong>
<div>X: {point.data.x}</div>
<div>Y: {point.data.y}</div>
</div>
)}
При необходимости tooltip может быть связан с внешним состоянием:
Точки являются отдельным уровнем интерактивности. Их можно
модифицировать через pointComponent, заменяя стандартное
поведение на кастомное.
const CustomPoint = ({ point }) => (
<circle
cx={point.x}
cy={point.y}
r={6}
fill={point.color}
stroke="black"
strokeWidth={1}
/>
);
При этом важно учитывать производительность: при большом количестве точек кастомные компоненты могут увеличивать нагрузку на рендер.
Интерактивность часто выходит за пределы одного графика. Типичный
сценарий — синхронизация нескольких ResponsiveLine через
общий state.
const [hoveredX, setHoveredX] = useState(null);
<ResponsiveLine
onMouseM ove={(point) => setHoveredX(point.data.x)}
onMouseLe ave={() => setHoveredX(null)}
/>
Далее это состояние может использоваться:
Nivo позволяет менять стиль линий в зависимости от состояния:
strokeWidth: serie.id === activeSerie ? 4 : 2,
strokeOpacity: activeSerie && serie.id !== activeSerie ? 0.2 : 1
Комбинация этих параметров формирует визуальную иерархию, которая ускоряет восприятие данных.
Анимации играют ключевую роль в интерактивности. Nivo использует встроенный motion engine для плавных переходов между состояниями графика.
Основные параметры:
animatemotionConfig<ResponsiveLine
animate={true}
motionConfig="stiff"
/>
Плавные переходы особенно заметны при:
При высокой плотности точек стандартный hover становится недостаточно точным. В таких случаях используется:
useMesh для расширения зоны взаимодействияenablePoints: false)Комбинация этих техник снижает шум и повышает точность взаимодействия.
Система layers позволяет управлять порядком отрисовки
интерактивных элементов.
<ResponsiveLine
layers={[
'grid',
'markers',
'axes',
customLayer,
'points',
'slices',
'mesh',
'legends'
]}
/>
Перемещение кастомного слоя выше или ниже точек позволяет создавать эффекты:
При сложных графиках ключевой задачей становится снижение визуального шума во время взаимодействия. Основные методы:
Эти техники позволяют сохранять читаемость даже при десятках серий данных.
Интерактивность Nivo часто используется не изолированно, а как источник событий для внешнего интерфейса:
Такой подход превращает график в управляющий элемент аналитической системы, а не просто визуализацию.