Создание кастомных тем

Vega и Vega-Lite используют декларативный подход к описанию визуализаций, в котором внешний вид отделён от структуры данных и логики построения графиков. Темы выступают как слой конфигурации, задающий единые визуальные правила оформления: цвета, шрифты, сетки, толщины линий, стили подписей и поведение интерактивных элементов.

Ключевая особенность системы тем заключается в том, что она не изменяет спецификацию графика, а накладывается поверх неё через механизм конфигурационного слияния (configuration merging). Это позволяет переиспользовать одну и ту же визуализацию в разных стилях без модификации описания данных.


Базовая структура темы

Тема в Vega/Vega-Lite представляет собой JavaScript-объект (или JSON-структуру), который переопределяет значения в конфигурации визуализации.

Основные секции:

  • background — фон всей визуализации
  • title — стили заголовков
  • axis — оформление осей
  • legend — стили легенды
  • range — цветовые и шкальные диапазоны
  • mark — стили графических примитивов
  • view — параметры контейнера визуализации

Пример минимальной темы:

const theme = {
  background: "#ffffff",
  title: {
    font: "Inter",
    fontSize: 16,
    color: "#222"
  },
  axis: {
    labelFont: "Inter",
    titleFont: "Inter",
    labelColor: "#555",
    domainColor: "#ccc",
    gridColor: "#eee"
  },
  mark: {
    color: "#4c78a8"
  }
};

Механизм применения темы

В Vega тема применяется через конфигурацию runtime или через регистрацию темы в глобальном реестре.

Регистрация темы

import { register } from "vega-themes";

register("customTheme", theme);

После регистрации тема становится доступной для использования в визуализациях:

const runtime = vega.parse(spec);
const view = new vega.View(runtime).renderer("canvas");

view.runAsync();

Или с явным указанием конфигурации:

const view = new vega.View(runtime, {
  config: theme
});

Темы в Vega-Lite

В Vega-Lite тема задаётся через поле config в спецификации:

const spec = {
  data: { url: "data.csv" },
  mark: "line",
  encoding: {
    x: { field: "date", type: "temporal" },
    y: { field: "value", type: "quantitative" }
  },
  config: theme
};

При компиляции Vega-Lite преобразует спецификацию в Vega и переносит конфигурацию на уровень Vega runtime.


Приоритет и слияние конфигураций

Система тем работает по принципу многоуровневого переопределения:

  1. Базовые значения Vega/Vega-Lite
  2. Глобальная тема
  3. Конфигурация спецификации
  4. Локальные стили конкретных mark-ов

При конфликте значений более высокий уровень перекрывает нижний.

Пример приоритетов:

default < theme < spec.config < mark encoding style

Кастомизация осей

Оси являются одной из наиболее часто настраиваемых частей темы.

const axisTheme = {
  axis: {
    domain: true,
    domainColor: "#333",
    domainWidth: 1,

    grid: true,
    gridColor: "#f0f0f0",
    gridDash: [4, 2],

    tickColor: "#333",
    tickSize: 5,

    labelFont: "Roboto",
    labelFontSize: 11,
    labelColor: "#444",

    titleFont: "Roboto",
    titleFontSize: 12,
    titleColor: "#111"
  }
};

Оси в Vega делятся на типы:

  • x
  • y
  • xOffset
  • yOffset

Каждый тип может быть переопределён отдельно:

axisX: {
  labelAngle: 0,
  labelPadding: 8
}

Стилизация mark-элементов

Mark-элементы определяют визуальные примитивы: линии, точки, прямоугольники, текст.

const markTheme = {
  mark: {
    color: "#1f77b4",
    opacity: 0.9
  },
  area: {
    fill: "#aec7e8",
    opacity: 0.6
  },
  line: {
    strokeWidth: 2,
    strokeJoin: "round"
  },
  point: {
    size: 60,
    filled: true
  }
};

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


Цветовые схемы и range-конфигурация

Темы управляют палитрами через range:

const colorTheme = {
  range: {
    category: [
      "#4e79a7",
      "#f28e2b",
      "#e15759",
      "#76b7b2",
      "#59a14f"
    ],
    diverging: [
      "#d73027",
      "#fdae61",
      "#ffffbf",
      "#a6d96a",
      "#1a9850"
    ]
  }
};

Для количественных шкал используются интерполяции:

range: {
  heatmap: ["#f7fbff", "#08306b"]
}

Типографика в темах

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

const typographyTheme = {
  title: {
    font: "Helvetica Neue",
    fontWeight: "bold",
    fontSize: 18
  },
  text: {
    font: "Helvetica Neue",
    fontSize: 12
  },
  legend: {
    labelFont: "Helvetica Neue",
    labelFontSize: 11,
    titleFont: "Helvetica Neue"
  }
};

Vega не ограничивает выбор шрифтов, но их поддержка зависит от рендерера (canvas или SVG).


Наследование и композиция тем

Темы могут быть композиционными. Базовая тема расширяется специализированными слоями.

const baseTheme = {
  background: "#fff",
  axis: { domainColor: "#ccc" }
};

const darkTheme = {
  ...baseTheme,
  background: "#1e1e1e",
  axis: {
    ...baseTheme.axis,
    labelColor: "#ddd",
    domainColor: "#666"
  }
};

Композиция позволяет строить:

  • светлые/тёмные режимы
  • корпоративные стили
  • тематические вариации (heatmaps, financial charts, scientific plots)

Использование сигналов и динамических тем

В Vega возможно использование signals для динамической смены темы:

signals: [
  {
    name: "themeMode",
    value: "light"
  }
]

И далее привязка к стилям:

config: {
  background: { signal: "themeMode === 'dark' ? '#000' : '#fff'" }
}

Такой подход позволяет переключать темы без пересборки спецификации.


Интеграция тем в pipeline визуализации

Процесс применения темы в Vega проходит несколько стадий:

  1. Парсинг спецификации
  2. Объединение config и theme
  3. Компиляция dataflow graph
  4. Рендеринг marks

В Vega-Lite добавляется дополнительный этап:

  • трансляция high-level спецификации в Vega runtime

Расширенные паттерны кастомизации

Темы для доменной визуализации

Финансовые графики:

const financeTheme = {
  axis: {
    grid: true,
    gridColor: "#e6e6e6"
  },
  mark: {
    line: {
      strokeWidth: 1.5
    }
  },
  range: {
    category: ["#2c3e50", "#e74c3c", "#27ae60"]
  }
};

Научные визуализации:

const scienceTheme = {
  axis: {
    labelFontSize: 10,
    tickCount: 5
  },
  mark: {
    point: {
      size: 40
    }
  }
};

Ограничения и особенности архитектуры тем

  • тема не может изменять структуру данных
  • тема не влияет на transforms (filter, aggregate)
  • сложные зависимости требуют сигналов, а не конфигурации
  • при глубокой вложенности конфигурации возможны конфликты переопределений

Взаимодействие темы с интерпретатором Vega

Рендерер Vega интерпретирует тему как часть runtime-конфигурации. Это означает, что:

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

При обновлении темы возможен частичный rerender без пересборки всей спецификации.


Организация библиотеки тем

Типичная структура набора тем:

themes/
  light.js
  dark.js
  corporate.js
  scientific.js
  index.js

Центральный реестр:

export const themes = {
  light,
  dark,
  corporate,
  scientific
};

Влияние тем на масштабируемость визуальных систем

Использование тем позволяет:

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

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