Интерактивность и выделение линий

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

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

Ключевым механизмом выступает система событий:

  • onMouseEnter
  • onMouseMove
  • onMouseLeave

Эти обработчики позволяют синхронизировать внешний 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
        }
      }
    ]
  }
]}

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


Slice-интерактивность и группировка значений

Механизм 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 и управление контекстом

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

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


Работа с motion и плавностью переходов

Анимации играют ключевую роль в интерактивности. Nivo использует встроенный motion engine для плавных переходов между состояниями графика.

Основные параметры:

  • animate
  • motionConfig
<ResponsiveLine
  animate={true}
  motionConfig="stiff"
/>

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

  • изменении данных
  • переключении активной линии
  • появлении/скрытии серий

Поведенческие паттерны при плотных данных

При высокой плотности точек стандартный hover становится недостаточно точным. В таких случаях используется:

  • useMesh для расширения зоны взаимодействия
  • отключение точек (enablePoints: false)
  • переход на slice-интерактивность

Комбинация этих техник снижает шум и повышает точность взаимодействия.


Кастомизация уровней рендера через layers

Система layers позволяет управлять порядком отрисовки интерактивных элементов.

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

Перемещение кастомного слоя выше или ниже точек позволяет создавать эффекты:

  • линии поверх точек
  • подсветка поверх графика
  • затемнение фона при активной серии

Контроль визуального шума при взаимодействии

При сложных графиках ключевой задачей становится снижение визуального шума во время взаимодействия. Основные методы:

  • уменьшение opacity неактивных линий
  • временное скрытие второстепенных серий
  • использование legend-based filtering
  • переключение режимов hover/slice

Эти техники позволяют сохранять читаемость даже при десятках серий данных.


Связка событий и внешнего UI

Интерактивность Nivo часто используется не изолированно, а как источник событий для внешнего интерфейса:

  • таблицы данных синхронизируются с hover
  • фильтры обновляются через click по легенде
  • панели деталей обновляются при выборе точки

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