Tooltip и всплывающие окна

Механизм всплывающих подсказок в Kepler.gl строится вокруг системы интерактивного слоя визуализации, где каждый объект на карте может реагировать на наведение курсора и возвращать структурированные данные из датасета. Основная идея заключается не в «рисовании окна», а в привязке логики отображения к состоянию визуализации (visState) и событиям взаимодействия.

Вся интерактивность в Kepler.gl контролируется через visState.interactionConfig. Этот объект определяет, какие типы взаимодействий включены, какие данные доступны пользователю и как они форматируются перед отображением.

Ключевые элементы конфигурации:

  • tooltip — настройка всплывающих подсказок
  • brush — выделение области
  • geocoder — поиск по карте
  • interactionConfig.config — общий контейнер параметров взаимодействия

В контексте tooltip основное значение имеет структура:

interactionConfig: {
  tooltip: {
    enabled: true,
    fieldsToShow: {
      layer_id: ['field1', 'field2']
    },
    compareMode: false,
    compareType: 'absolute',
    customTitle: null
  }
}

Привязка tooltip к слоям

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

fieldsToShow: {
  trips_layer: ['start_time', 'end_time', 'distance']
}

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

Формирование данных для всплывающего окна

При наведении курсора Kepler.gl выполняет запрос к активным слоям через механизм deck.gl picking. Каждый слой возвращает объект pickedObject, содержащий:

  • index — индекс объекта в данных
  • layer — ссылка на слой
  • object — исходная запись данных
  • coordinate — географическая позиция
  • pixel — экранные координаты

На основе этого объекта формируется содержимое tooltip.

Внутренняя логика напоминает следующий процесс:

function getTooltipInfo(pickedObject, fieldsToShow) {
  const layerId = pickedObject.layer.id;
  const fields = fieldsToShow[layerId];

  return fields.map(field => ({
    name: field,
    value: pickedObject.object[field]
  }));
}

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

Kepler.gl поддерживает базовую систему форматирования значений через кастомные форматтеры. Они определяются на уровне датасета или глобальной конфигурации.

Типичные сценарии:

  • форматирование чисел (округление, разделители тысяч)
  • преобразование временных меток
  • добавление единиц измерения

Пример форматтера:

const formatters = {
  distance: value => `${value.toFixed(2)} км`,
  timestamp: value => new Date(value).toISOString()
};

Форматтеры применяются до рендера tooltip, обеспечивая консистентность отображения независимо от источника данных.

HTML-рендеринг tooltip

Хотя стандартный tooltip Kepler.gl представляет собой структурированный список полей, возможна кастомизация через HTML-шаблоны. В этом случае разработчик получает полный контроль над содержимым всплывающего окна.

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

tooltip: {
  enabled: true,
  html: object => `
    <div class="tooltip-container">
      <div class="title">${object.name}</div>
      <div class="value">${object.value}</div>
    </div>
  `
}

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

Отличие tooltip от popup

В Kepler.gl важно различать два уровня всплывающей информации:

  • Tooltip — легковесное окно, привязанное к наведению курсора
  • Popup (Mapbox GL интеграция) — более тяжёлое окно, часто фиксированное и интерактивное

Tooltip работает внутри rendering pipeline deck.gl и обновляется на каждый mouse move, тогда как popup чаще создаётся как отдельный DOM-слой через Mapbox GL API.

Поведение при пересечении слоёв

При наличии нескольких слоёв под курсором Kepler.gl формирует стек объектов. Приоритет определяется:

  1. Порядком слоёв (z-index логика)
  2. Типом геометрии (polygons > lines > points в некоторых конфигурациях)
  3. Активностью слоя

Результирующий tooltip может агрегировать данные:

[
  { layer: 'points', value: {...} },
  { layer: 'heatmap', value: {...} }
]

Compare mode в tooltip

Режим сравнения (compareMode) позволяет отображать несколько значений одного и того же поля из разных объектов. Это используется для анализа изменений во времени или пространстве.

Структура сравнения:

compareMode: true,
compareType: 'relative'

В этом режиме tooltip перестаёт быть привязан к одному объекту и начинает работать как агрегатор выборки.

Взаимодействие с событиями карты

Tooltip тесно связан с системой событий Kepler.gl:

  • onLayerHover
  • onMapHover
  • onInteractionStateChange

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

Пример обработки:

function onMapHover(info) {
  if (info.picked && info.object) {
    return transformTooltip(info.object);
  }
}

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

При высокой плотности данных tooltip становится критическим элементом производительности. Основные оптимизации:

  • ограничение числа полей в fieldsToShow
  • отключение tooltip для слоёв с высокой частотой обновления
  • кэширование форматированных значений
  • минимизация DOM-рендера через мемоизацию

Deck.gl использует WebGL picking, но финальная сборка tooltip происходит в JavaScript, поэтому узким местом становится именно слой представления.

Кастомные сценарии отображения

Kepler.gl допускает расширение поведения tooltip через модификацию Redux-состояния. Это позволяет:

  • динамически менять набор отображаемых полей
  • подгружать дополнительные данные по API
  • формировать вычисляемые значения на лету

Пример динамического обновления:

dispatch(
  updateInteractionConfig({
    tooltip: {
      fieldsToShow: {
        layer_1: ['speed', 'altitude', 'heading']
      }
    }
  })
);

Геометрическая привязка tooltip

Позиционирование tooltip основано на screen-space координатах, вычисляемых из Web Mercator проекции. При этом учитываются:

  • смещение камеры (pitch, bearing)
  • масштаб (zoom level)
  • z-index слоя

Tooltip не является частью WebGL сцены, он рендерится поверх канваса, что требует синхронизации координат при каждом обновлении камеры.