Поле labelOverlap: устранение перекрытий

Поле labelOverlap в Vega и Vega-Lite отвечает за автоматическое устранение наложения текстовых подписей на осях. При высокой плотности данных подписи осей начинают пересекаться, становясь нечитаемыми. Механизм labelOverlap управляет стратегией отбора или скрытия части меток так, чтобы сохранить визуальную ясность без ручной настройки каждого тика.

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

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

подписи начинают накладываться друг на друга. Это приводит к потере читаемости и визуальному шуму.

Механизм labelOverlap вмешивается на этапе рендеринга и применяет алгоритм фильтрации меток.

Расположение свойства в спецификации

В Vega-Lite свойство задаётся внутри описания оси:

{
  "mark": "line",
  "encoding": {
    "x": {
      "field": "date",
      "type": "temporal",
      "axis": {
        "labelOverlap": "greedy"
      }
    },
    "y": {
      "field": "value",
      "type": "quantitative"
    }
  }
}

В Vega (низкоуровневой спецификации) аналогичная логика применяется через настройки axis:

{
  "axes": [
    {
      "scale": "xscale",
      "orient": "bottom",
      "labelOverlap": true
    }
  ]
}

Режимы работы labelOverlap

boolean-режим

Исторически использовалось логическое значение:

  • true — включение стандартного алгоритма устранения перекрытий
  • false — отключение обработки, все метки отображаются

В современных версиях Vega-Lite boolean-режим считается упрощённым интерфейсом.

greedy-алгоритм

Режим "greedy" реализует жадный алгоритм отбора:

  • метки сортируются по позиции на оси
  • каждая следующая метка проверяется на пересечение с уже выбранными
  • если пересечение есть — метка скрывается

Особенности:

  • максимальная плотность отображения
  • высокая скорость работы
  • отсутствие глобальной оптимизации

Применение:

{
  "axis": {
    "labelOverlap": "greedy"
  }
}

parity-алгоритм

Режим "parity" использует более сбалансированный подход:

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

Применение:

{
  "axis": {
    "labelOverlap": "parity"
  }
}

Особенности:

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

Алгоритмическая природа фильтрации

Оба алгоритма работают на уровне пост-обработки шкалы после вычисления tick values. Порядок обработки:

  1. вычисление списка tick-значений
  2. генерация текстовых меток через форматтер
  3. вычисление bounding box каждой метки
  4. проверка пересечений
  5. исключение части элементов согласно стратегии

Ключевым ограничением является то, что labelOverlap не изменяет шкалу — он лишь скрывает часть визуальных элементов.

Влияние масштаба и плотности данных

Поведение labelOverlap напрямую зависит от типа шкалы:

Непрерывные шкалы

Для числовых и временных осей:

  • количество меток часто велико
  • вероятность перекрытия высокая
  • labelOverlap активно уменьшает плотность

Дискретные шкалы

Для категориальных осей:

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

Взаимодействие с другими параметрами оси

labelAngle

Поворот подписей влияет на частоту срабатывания labelOverlap:

{
  "axis": {
    "labelAngle": -45,
    "labelOverlap": "greedy"
  }
}

При наклоне текста количество пересечений уменьшается, и алгоритм может сохранять больше меток.

labelLimit и labelLimit (ограничение длины)

Сокращение текста уменьшает bounding box:

  • меньше пересечений
  • более плотное отображение
  • снижение агрессивности фильтрации

tickCount

Параметр tickCount задаёт желаемое количество делений:

  • уменьшение tickCount снижает необходимость overlap-обработки
  • увеличение увеличивает нагрузку на алгоритм

Практика применения в Vega-Lite

Временные ряды

{
  "mark": "line",
  "encoding": {
    "x": {
      "field": "timestamp",
      "type": "temporal",
      "axis": {
        "labelOverlap": "parity",
        "labelAngle": 0
      }
    },
    "y": {
      "field": "temperature",
      "type": "quantitative"
    }
  }
}

Временные оси часто содержат плотные значения, поэтому parity обеспечивает равномерное прореживание.

Категориальные данные с длинными подписями

{
  "mark": "bar",
  "encoding": {
    "x": {
      "field": "category",
      "type": "nominal",
      "axis": {
        "labelAngle": -40,
        "labelOverlap": "greedy"
      }
    },
    "y": {
      "field": "count",
      "type": "quantitative"
    }
  }
}

Здесь жадный алгоритм сохраняет максимальное количество категорий, скрывая только конфликтующие.

Ограничения механизма

labelOverlap не решает фундаментальные проблемы перегруженных осей:

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

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

Особенности рендеринга

Внутренне Vega вычисляет bounding boxes меток уже после применения форматирования текста. Это означает:

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

Сочетание с multi-line метками

Многострочные подписи увеличивают вертикальный размер bounding box, что усиливает вероятность перекрытий:

{
  "axis": {
    "labelExpr": "datum.label + '\\n' + datum.value",
    "labelOverlap": "greedy"
  }
}

В таких случаях parity часто даёт более стабильный результат, чем greedy.

Производительность

Алгоритмы labelOverlap оптимизированы под линейную сложность относительно количества меток:

  • greedy: O(n²) в худшем случае, но обычно ближе к O(n)
  • parity: O(n)

При больших наборах данных разница становится заметной, особенно в интерактивных визуализациях.

Поведение при ресайзе

При изменении размеров контейнера:

  • пересчитываются tick values
  • повторно запускается алгоритм overlap
  • набор отображаемых меток может динамически изменяться

Это приводит к “плавающему” количеству подписей при responsive-визуализациях.