Highlight и multi-select паттерны

В Vega-Lite интерактивность строится вокруг декларативных selection-конструкций, которые связываются с визуальными каналами через condition. Наиболее распространённый сценарий — highlight-паттерн, при котором один элемент визуализации подсвечивается при наведении или выборе, а остальные элементы остаются в приглушённом состоянии.

Ключевая идея заключается в разделении состояния данных на два слоя:

  • активный элемент (selected / hovered)
  • все остальные элементы (unselected)

Hover-highlight через single selection

Наиболее простой вариант реализуется через selection с типом single и on: "mouseover":

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": { "url": "data/cars.json" },

  "selection": {
    "highlight": {
      "type": "single",
      "on": "mouseover",
      "empty": "none"
    }
  },

  "mark": "point",

  "encoding": {
    "x": { "field": "Horsepower", "type": "quantitative" },
    "y": { "field": "Miles_per_Gallon", "type": "quantitative" },

    "color": {
      "condition": {
        "selection": "highlight",
        "value": "#e45756"
      },
      "value": "#d3d3d3"
    },

    "size": {
      "condition": {
        "selection": "highlight",
        "value": 120
      },
      "value": 40
    }
  }
}

В этой конфигурации:

  • selection.highlight фиксирует активную точку под курсором
  • condition.selection управляет визуальным откликом
  • невыбранные элементы получают нейтральный стиль

Деконструкция highlight-паттерна

Highlight-паттерн почти всегда включает три слоя логики:

  1. Событие (on) Определяет, как активируется состояние:

    • mouseover — наведение
    • click — фиксация выбора
    • комбинированные события через Vega expressions
  2. Состояние selection

    • single — один активный объект
    • multi — набор активных объектов
  3. Визуальная реакция

    • цвет (color)
    • прозрачность (opacity)
    • размер (size)
    • форма (shape)
    • обводка (stroke)

Multi-select паттерн: управление множественными выделениями

Для аналитических интерфейсов более характерен сценарий, где пользователь выделяет сразу несколько элементов. В Vega-Lite это реализуется через type: "multi".

Базовый multi-select через click

{
  "data": { "url": "data/cars.json" },

  "selection": {
    "picked": {
      "type": "multi",
      "on": "click",
      "toggle": "event.shiftKey"
    }
  },

  "mark": "circle",

  "encoding": {
    "x": { "field": "Horsepower", "type": "quantitative" },
    "y": { "field": "Acceleration", "type": "quantitative" },

    "color": {
      "condition": {
        "selection": "picked",
        "value": "#4c78a8"
      },
      "value": "#c0c0c0"
    },

    "opacity": {
      "condition": {
        "selection": "picked",
        "value": 1
      },
      "value": 0.2
    }
  }
}

Особенности поведения:

  • одиночный клик добавляет элемент в выбор
  • Shift переключает режим toggle
  • состояние selection представляет множество выбранных точек
  • визуальная деградация невыбранных элементов усиливает фокус

Паттерн «dim others»: управление контрастом

Одним из наиболее используемых подходов является не столько выделение выбранных элементов, сколько приглушение остальных.

Этот паттерн особенно полезен в плотных scatterplot или bar chart.

"opacity": {
  "condition": {
    "selection": "picked",
    "value": 1
  },
  "value": 0.15
}

Семантика проста:

  • выбранные → нормальная непрозрачность
  • остальные → визуально второстепенные

Этот подход лучше масштабируется, чем усиление выбранных элементов, особенно при большом количестве точек.


Legend-based multi-select

В Vega-Lite selection может быть привязан к легенде, что позволяет использовать легенду как интерфейс фильтрации.

{
  "selection": {
    "byCategory": {
      "type": "multi",
      "fields": ["Origin"],
      "bind": "legend"
    }
  },

  "mark": "bar",

  "encoding": {
    "x": { "aggregate": "count" },
    "y": { "field": "Origin", "type": "nominal" },

    "color": {
      "condition": {
        "selection": "byCategory",
        "field": "Origin",
        "type": "nominal"
      },
      "value": "lightgray"
    }
  }
}

Здесь легенда становится управляющим интерфейсом:

  • клик по категории включает/исключает её
  • selection хранит набор значений поля
  • визуализация превращается в интерактивный фильтр

Interval selection: brush-паттерн

Brush selection используется для выделения диапазонов.

{
  "selection": {
    "brush": {
      "type": "interval"
    }
  },

  "mark": "point",

  "encoding": {
    "x": { "field": "Horsepower", "type": "quantitative" },
    "y": { "field": "Miles_per_Gallon", "type": "quantitative" },

    "opacity": {
      "condition": {
        "selection": "brush",
        "value": 1
      },
      "value": 0.1
    }
  }
}

Поведение:

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

Interval selection часто комбинируется с resolve: "global" при нескольких слоях графика.


Nearest selection: паттерн «умного hover»

При плотных точечных графиках курсор редко попадает точно в точку. Для этого используется nearest: true.

"selection": {
  "hover": {
    "type": "single",
    "on": "mouseover",
    "nearest": true,
    "empty": "none"
  }
}

Механика:

  • выбирается ближайший объект к курсору
  • расстояние вычисляется в пиксельном пространстве
  • взаимодействие становится «липким» к данным

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


Комбинированные highlight + multi-select схемы

В реальных интерфейсах часто используются сразу несколько selection-состояний.

Пример комбинированной модели:

"selection": {
  "hover": {
    "type": "single",
    "on": "mouseover",
    "nearest": true
  },

  "picked": {
    "type": "multi",
    "on": "click",
    "toggle": "event.shiftKey"
  }
}

И логика визуализации:

  • hover → временный highlight
  • picked → устойчивое выделение
  • hover может усиливать picked или быть независимым слоем

Приоритеты состояний: конфликт highlight и selection

Когда одновременно существуют hover и multi-select, возникает задача приоритизации:

Типичный порядок:

  1. hover (временное состояние)
  2. selected (фиксированное состояние)
  3. default (фон)

Реализация через condition с вложенными проверками:

"color": {
  "condition": [
    {
      "selection": "hover",
      "value": "#f58518"
    },
    {
      "selection": "picked",
      "value": "#4c78a8"
    }
  ],
  "value": "#d9d9d9"
}

Такой подход позволяет строить иерархические визуальные состояния.


Cross-filtering через multi-select

Selection может использоваться не только для визуального highlight, но и как фильтр для других слоёв.

"transform": [
  { "filter": { "selection": "picked" } }
]

Это превращает график в интерактивный фильтр:

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

Highlight в layered-графиках

В layered визуализациях (stacked + overlay) selection применяется выборочно:

  • слой точек → интерактивный highlight
  • слой линий → статическая структура
  • слой аннотаций → реакция на selection
{
  "layer": [
    {
      "mark": "line",
      "encoding": {
        "x": { "field": "year" },
        "y": { "field": "value" }
      }
    },
    {
      "mark": "point",
      "selection": {
        "hover": { "type": "single", "on": "mouseover", "nearest": true }
      },
      "encoding": {
        "x": { "field": "year" },
        "y": { "field": "value" },

        "size": {
          "condition": { "selection": "hover", "value": 120 },
          "value": 30
        }
      }
    }
  ]
}

Такой разделённый подход уменьшает визуальный шум и отделяет структуру от интерактивности.


Highlight через вычисляемые поля (Vega expressions)

В более гибких сценариях Vega позволяет использовать выражения:

"opacity": {
  "condition": "datum.selected === true",
  "value": 0.2
}

Или через expr:

"condition": {
  "test": "datum.origin === 'USA'",
  "value": "red"
}

Это даёт возможность реализовывать кастомные highlight-логики вне стандартного selection API.


Масштабирование multi-select на больших данных

При работе с тысячами и десятками тысяч точек highlight-паттерны требуют оптимизации:

  • использование nearest: true вместо точного hit-testing
  • минимизация числа condition-каналов
  • предпочтение opacity вместо size (дешевле рендерингом)
  • ограничение multi-select через фильтры полей

Особенно критично в canvas-renderer vs SVG:

  • SVG: удобно, но медленно при больших наборах
  • Canvas: лучше для dense scatter + highlight

Синхронизация selection между визуализациями

Selection может быть глобальным объектом, используемым в нескольких view:

"resolve": "global"

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

  • выделить категорию в bar chart
  • подсветить соответствующие точки в scatter plot
  • синхронизировать line chart

Модель поведения становится связанной системой состояний, а не отдельными графиками.