Всплывающие подсказки (tooltips)

В библиотеке deck.gl всплывающие подсказки (tooltips) реализуются как слой взаимодействия между системой рендеринга и механизмом picking-событий WebGL. Основная задача tooltip-логики заключается в отображении контекстной информации о визуальных объектах без нарушения производительности и без необходимости дополнительного DOM-рендеринга для каждого кадра.

В основе лежит процесс picking — определение объекта сцены под курсором. Deck.gl выполняет его на уровне GPU, используя специальный буфер идентификаторов, сопоставляемых с объектами слоёв.


Архитектура tooltip-системы

Механизм всплывающих подсказок опирается на три ключевых компонента:

1. Picking engine

  • Выполняет определение объекта под курсором
  • Использует уникальные object picking colors
  • Возвращает info структуру с данными слоя и объекта

2. Event pipeline

  • Обрабатывает события hover, click, drag
  • Передаёт результаты в callback-и приложения

3. Tooltip renderer

  • Отвечает за отображение DOM-элемента
  • Обычно реализуется через HTML overlay
  • Может быть полностью кастомизирован

Объект pickingInfo

При наведении курсора Deck.gl формирует объект info, который является основой для tooltip-логики:

{
  object: {},        // данные объекта слоя
  index: 12,         // индекс элемента
  picked: true,      // был ли объект найден
  x: 350,           // координата курсора X
  y: 220,           // координата курсора Y
  layer: {},        // слой, который вернул объект
  viewport: {}      // текущая вьюпорт-конфигурация
}

На основе этих данных формируется содержимое всплывающей подсказки.


Использование getTooltip в DeckGL

В React-интеграции через react deck.gl tooltip чаще всего определяется через функцию getTooltip.

import DeckGL from '@deck.gl/react';
import {ScatterplotLayer} from '@deck.gl/layers';

const layer = new ScatterplotLayer({
  id: 'scatter',
  data: points,
  getPosition: d => d.position,
  getRadius: 100,
  getFillColor: [255, 0, 0]
});

function getTooltip({object}) {
  if (!object) return null;
  return {
    html: `<div><b>${object.name}</b><br/>Value: ${object.value}</div>`,
    style: {
      backgroundColor: 'white',
      color: 'black',
      fontSize: '12px'
    }
  };
}

export default function App() {
  return (
    <DeckGL
      initialViewState={{longitude: 0, latitude: 0, zoom: 2}}
      controller={true}
      layers={[layer]}
      getTooltip={getTooltip}
    />
  );
}

Функция getTooltip вызывается при каждом движении мыши и получает актуальный pickingInfo.


Формирование содержимого tooltip

Tooltip может возвращать различные форматы:

HTML-строка

return {
  html: `<div>${object.label}</div>`
};

Текстовый формат

return object.label;

DOM-стилизация

return {
  html: `<div>${object.label}</div>`,
  style: {
    background: 'rgba(0,0,0,0.7)',
    color: '#fff',
    padding: '8px'
  }
};

Tooltip без React (Deck класс)

В низкоуровневом API через класс Deck tooltip реализуется через callback getTooltip или обработчик событий.

import {Deck} from '@deck.gl/core';
import {ScatterplotLayer} from '@deck.gl/layers';

const deckgl = new Deck({
  initialViewState: {
    longitude: 10,
    latitude: 50,
    zoom: 4
  },
  controller: true,
  layers: [
    new ScatterplotLayer({
      id: 'points',
      data: points,
      getPosition: d => d.position,
      getRadius: 5000,
      pickable: true
    })
  ],
  getTooltip: ({object}) =>
    object && {
      text: `${object.name}: ${object.value}`
    }
});

Ключевое требование — pickable: true в слое, иначе picking не активируется.


Управление отображением tooltip через onHover

Низкоуровневый контроль достигается через обработчик onHover, где tooltip полностью выносится в пользовательский DOM.

const deckgl = new Deck({
  layers: [layer],
  onHover: info => {
    const tooltip = document.getElementById('tooltip');

    if (info.object) {
      tooltip.style.display = 'block';
      tooltip.style.left = info.x + 'px';
      tooltip.style.top = info.y + 'px';
      tooltip.innerHTML = info.object.name;
    } else {
      tooltip.style.display = 'none';
    }
  }
});

Этот подход позволяет:

  • контролировать позиционирование
  • оптимизировать рендеринг
  • интегрировать сторонние UI-библиотеки

Координатное позиционирование tooltip

Deck.gl не управляет абсолютным позиционированием tooltip автоматически в низкоуровневом режиме. Обычно используются экранные координаты:

  • info.x
  • info.y

Пример CSS:

.tooltip {
  position: absolute;
  pointer-events: none;
  transform: translate(10px, 10px);
}

Взаимодействие tooltip и multiple layers

При наличии нескольких слоёв поведение определяется:

  • приоритетом слоя (layer order)
  • значением pickable
  • глубиной пикселя (z-buffer)

Первый подходящий объект возвращается как info.object, даже если под курсором находится несколько слоёв.


Кастомизация логики выбора объекта

При необходимости tooltip можно формировать на основе нескольких объектов:

getTooltip: (info) => {
  if (!info.picked) return null;

  return {
    html: `
      <div>
        Layer: ${info.layer.id}<br/>
        Index: ${info.index}<br/>
        Value: ${info.object.value}
      </div>
    `
  };
};

Оптимизация частоты обновления tooltip

При движении мыши callback может вызываться десятки раз в секунду. Для снижения нагрузки применяются:

  • кеширование последнего объекта
  • сравнение info.index
  • debounce на уровне DOM-обновлений
let lastId = null;

function getTooltip(info) {
  if (!info.object || info.index === lastId) return null;
  lastId = info.index;

  return {
    text: info.object.name
  };
}

Tooltip в WebGL контексте

В отличие от DOM-ориентированных библиотек, Deck.gl работает поверх WebGL, поэтому:

  • объекты не имеют DOM-узлов
  • hit detection выполняется через GPU picking pass
  • tooltip формируется полностью вне сцены рендеринга

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


Интеграция с кастомными UI слоями

Tooltip часто используется совместно с внешними UI-фреймворками:

  • React portals
  • Vue overlays
  • pure DOM layers

Пример через portal:

function Tooltip({info}) {
  if (!info.object) return null;

  return createPortal(
    <div className="tooltip">
      {info.object.name}
    </div>,
    document.body
  );
}

Поведение при отсутствии объекта

Когда курсор не попадает на объект:

  • info.object === null
  • tooltip должен быть скрыт
  • DOM-элемент не обновляется

Это важно для предотвращения мерцания интерфейса при движении курсора по пустым областям карты или сцены.


Расширенные сценарии использования

Tooltip в Deck.gl применяется не только для отображения данных, но и для:

  • динамического анализа геоданных
  • визуализации временных рядов
  • отображения агрегированных значений
  • debugging слоёв и атрибутов

В сложных сценах tooltip становится инструментом интроспекции слоя, предоставляя доступ к внутренним данным визуализации без изменения рендера сцены.