Привязка выделения к фильтрации: filter transform

В библиотеке Vega-Lite интерактивность строится вокруг механизма selection и системы преобразований данных (transforms). Одним из ключевых инструментов становится filter transform, позволяющий динамически изменять отображаемый набор данных в зависимости от действий пользователя.

Фильтрация через выделение используется в следующих сценариях:

  • выделение диапазона на графике;
  • выбор категорий;
  • интерактивные панели управления;
  • перекрёстная фильтрация между несколькими визуализациями;
  • построение дашбордов;
  • drill-down анализ данных.

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


Архитектура взаимодействия

Механизм состоит из трёх частей:

  1. Параметр (params)
  2. Выделение (select)
  3. Фильтр (transform.filter)

Схема работы:

Пользователь → selection → параметр → filter transform → обновление графика

Базовая фильтрация через selection

Простейший пример связывает два графика:

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

Пример: фильтрация точек через interval selection

const spec = {
  data: {
    url: "data/cars.json"
  },

  vconcat: [
    {
      params: [
        {
          name: "brush",
          select: "interval"
        }
      ],

      mark: "point",

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

    {
      transform: [
        {
          filter: { param: "brush" }
        }
      ],

      mark: "bar",

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

Как работает filter transform

Ключевой блок:

transform: [
  {
    filter: { param: "brush" }
  }
]

означает:

Оставить только те строки данных,
которые попали в выделение brush

Если выделение пустое, поведение зависит от конфигурации selection.


Типы selection для фильтрации

interval

Выделение диапазона.

select: "interval"

Подходит для:

  • zoom-like поведения;
  • выделения областей;
  • анализа плотности;
  • crossfilter.

point

Выделение отдельных элементов.

select: "point"

Используется для:

  • выбора категорий;
  • фильтрации по клику;
  • интерактивных легенд.

Фильтрация через point selection

Пример выбора категории

const spec = {
  data: {
    values: [
      { category: "A", value: 10 },
      { category: "B", value: 20 },
      { category: "C", value: 30 }
    ]
  },

  params: [
    {
      name: "selected",
      select: "point"
    }
  ],

  hconcat: [
    {
      mark: "bar",

      encoding: {
        x: { field: "category", type: "nominal" },
        y: { field: "value", type: "quantitative" },
        color: {
          condition: {
            param: "selected",
            value: "orange"
          },
          value: "steelblue"
        }
      }
    },

    {
      transform: [
        {
          filter: { param: "selected" }
        }
      ],

      mark: "text",

      encoding: {
        text: { field: "category" }
      }
    }
  ]
};

Особенности работы filter

Фильтрация происходит после загрузки данных

Transform не изменяет источник данных.

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


Filter участвует в pipeline преобразований

Порядок transforms имеет значение.

transform: [
  { filter: { param: "brush" } },
  { aggregate: [...] }
]

и

transform: [
  { aggregate: [...] },
  { filter: { param: "brush" } }
]

дают разные результаты.


Порядок transforms

Pipeline выполняется сверху вниз.

Сначала filter

transform: [
  {
    filter: { param: "brush" }
  },

  {
    aggregate: [
      {
        op: "count",
        as: "count"
      }
    ]
  }
]

Агрегация выполняется только для выбранных данных.


Сначала aggregate

transform: [
  {
    aggregate: [
      {
        op: "count",
        as: "count"
      }
    ]
  },

  {
    filter: { param: "brush" }
  }
]

Сначала агрегируются все данные, затем применяется фильтр.

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


Empty selection

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

Все данные выбраны

Это поведение называется:

empty: "all"

Управление empty

Показывать всё при отсутствии выделения

params: [
  {
    name: "brush",
    select: {
      type: "interval"
    }
  }
]

Поведение по умолчанию.


Не показывать ничего

params: [
  {
    name: "brush",

    select: {
      type: "interval"
    }
  }
],

transform: [
  {
    filter: {
      param: "brush",
      empty: false
    }
  }
]

Теперь без выделения график будет пустым.


Связанные визуализации

Наиболее мощный сценарий — coordinated views.


Crossfilter

Crossfilter — техника, при которой несколько графиков взаимно фильтруют друг друга.


Пример crossfilter

const brush = {
  name: "brush",
  select: "interval"
};

const spec = {
  data: {
    url: "data/cars.json"
  },

  params: [brush],

  hconcat: [
    {
      mark: "point",

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

        y: {
          field: "Miles_per_Gallon",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          filter: { param: "brush" }
        }
      ],

      mark: "bar",

      encoding: {
        x: {
          field: "Origin",
          type: "nominal"
        },

        y: {
          aggregate: "count"
        }
      }
    }
  ]
};

Фильтрация нескольких графиков одновременно

Один selection может управлять множеством представлений.

transform: [
  {
    filter: { param: "brush" }
  }
]

может повторяться в любом количестве chart blocks.


Комбинирование selection и условий

Selection можно совмещать с обычными условиями filter.


Пример комбинированного filter

transform: [
  {
    filter: {
      and: [
        { param: "brush" },
        "datum.Horsepower > 100"
      ]
    }
  }
]

Фильтр означает:

Элемент находится в выделении
И
Horsepower > 100

Операторы filter

and

{
  and: [
    { param: "brush" },
    "datum.value > 10"
  ]
}

or

{
  or: [
    { param: "a" },
    { param: "b" }
  ]
}

not

{
  not: { param: "brush" }
}

Инверсия selection.


Инвертированная фильтрация

Полезна для:

  • отображения исключённых элементов;
  • comparison mode;
  • focus + context.

Пример focus + context

transform: [
  {
    filter: {
      not: { param: "brush" }
    }
  }
]

Показываются только элементы вне выделения.


Selection как источник логики

Selection в Vega-Lite — не просто UI-событие.

Это полноценное логическое состояние визуализации.

Его можно использовать:

  • в filter;
  • в condition;
  • в scale domains;
  • в вычислениях;
  • в выражениях.

Связь filter и condition

Очень распространённый паттерн:

condition → визуальное выделение
filter → изменение набора данных

Пример совместного использования

params: [
  {
    name: "selected",
    select: "point"
  }
],

mark: "point",

encoding: {
  color: {
    condition: {
      param: "selected",
      value: "red"
    },
    value: "gray"
  }
}

и:

transform: [
  {
    filter: { param: "selected" }
  }
]

Первый график подсвечивает точки.

Второй показывает только выбранные данные.


Фильтрация через legend binding

Selection может быть привязан к легенде.


Пример legend binding

params: [
  {
    name: "origin",
    select: {
      type: "point",
      fields: ["Origin"]
    },

    bind: "legend"
  }
]

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

transform: [
  {
    filter: { param: "origin" }
  }
]

Теперь легенда становится элементом управления фильтрацией.


Фильтрация по нескольким полям

Selection может учитывать несколько полей одновременно.


Пример

params: [
  {
    name: "sel",

    select: {
      type: "point",
      fields: ["Origin", "Cylinders"]
    }
  }
]

Filter будет учитывать комбинацию полей.


Выделение через nearest

Для scatter plot часто используется nearest selection.

select: {
  type: "point",
  nearest: true
}

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


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

Интерактивная фильтрация может становиться дорогой операцией.

Особенно при:

  • больших объёмах данных;
  • множестве coordinated views;
  • сложных transform chains;
  • nested specifications.

Оптимизация filter transform

Уменьшение объёма данных

Лучше предварительно агрегировать данные.


Минимизация transform chains

Плохо:

transform: [
  ...
  ...
  ...
  ...
  ...
]

Каждый шаг увеличивает стоимость перерасчёта.


Использование server-side aggregation

Для очень больших datasets:

Backend → агрегированные данные → Vega-Lite

вместо передачи миллионов строк.


Filter внутри layered charts

Filter можно применять отдельно к слоям.


Пример

layer: [
  {
    mark: "point"
  },

  {
    transform: [
      {
        filter: { param: "brush" }
      }
    ],

    mark: {
      type: "point",
      size: 200
    }
  }
]

Первый слой показывает все точки.

Второй — только выбранные.


Разница между filter и opacity condition

filter

Удаляет элементы из dataflow.


opacity/color condition

Элементы остаются в dataflow, но меняется отображение.


Сравнение подходов

filter

transform: [
  {
    filter: { param: "brush" }
  }
]

Преимущества:

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

Недостаток:

  • исчезают невыбранные данные.

condition

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

Преимущества:

  • сохраняется контекст;
  • лучше UX в exploratory analysis.

Недостаток:

  • все данные продолжают участвовать в рендеринге.

Filter и aggregate

Одно из самых важных применений selection filtering — динамические агрегаты.


Пример динамической агрегации

transform: [
  {
    filter: { param: "brush" }
  }
],

encoding: {
  y: {
    aggregate: "mean",
    field: "Miles_per_Gallon"
  }
}

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


Filter и lookup

Selection filter можно комбинировать с lookup transform.

transform: [
  {
    filter: { param: "selected" }
  },

  {
    lookup: "id",
    from: {
      data: {
        url: "details.json"
      },
      key: "id",
      fields: ["description"]
    }
  }
]

Filter и calculate

Часто используется последовательность:

filter → calculate

Пример

transform: [
  {
    filter: { param: "brush" }
  },

  {
    calculate: "datum.price * datum.quantity",
    as: "total"
  }
]

Вычисления выполняются только для выбранных данных.


Отладка selection filtering

Типичные проблемы:

  • selection не срабатывает;
  • filter не видит param;
  • пустой график;
  • конфликт transform order;
  • несовпадение полей.

Проверка имени param

Частая ошибка:

filter: { param: "brush" }

при:

name: "selection"

Имена должны совпадать.


Проверка области видимости

Selection работает только в пределах допустимого scope.

Например:

vconcat
hconcat
layer
repeat
facet

могут влиять на доступность параметра.


Filter внутри repeat

При использовании repeat важно учитывать наследование selection.

repeat: {
  column: ["Horsepower", "Acceleration"]
}

Selection может применяться ко всем повторяемым графикам одновременно.


Filter и reactive dataflow

Внутри Vega все transforms работают как реактивный поток данных.

Изменение selection автоматически:

  1. инвалидирует transform pipeline;
  2. пересчитывает filter;
  3. обновляет downstream transforms;
  4. инициирует новый render pass.

Именно поэтому interactive filtering выглядит мгновенным даже при сложных визуализациях.


Архитектурное значение filter transform

filter transform — центральный механизм интерактивной аналитики в Vega-Lite.

Он превращает пользовательские действия в реактивные преобразования данных и позволяет строить:

  • аналитические панели;
  • BI-интерфейсы;
  • exploratory dashboards;
  • coordinated visualizations;
  • crossfilter systems;
  • интерактивные исследовательские инструменты;
  • многосвязные представления данных.