Skip to content

Темы

Тема — это набор дизайн-токенов, из которого чарт берёт всё оформление: палитру серий, цвета фона и текста, базовый кегль, толщину линий, скругление и прозрачность марок, семантические цвета роста и падения, вид осей. В theme передаётся имя встроенной темы или объект ThemeOptions.

Собирать тему руками необязательно — в конструкторе тем на каждый токен есть контрол, живые превью и экспорт в JSON (страница на английском).

Встроенные темы

В библиотеке семь пресетов. Их имена экспортируются как THEME_NAMES — селекту не нужен захардкоженный список:

ts
import { Charts, THEME_NAMES, type ThemeName } from 'grafit-charts';

for (const name of THEME_NAMES) select.append(new Option(name, name));
void chart.updateDelta({ theme: select.value as ThemeName });
ИмяФонЧто это
'default'светлыйнейтральная светлая тема, используется без theme
'dark'тёмныйнейтральная тёмная тема
'vibrant'светлыйнасыщенные цвета в порядке, при котором соседние не сливаются у дальтоников
'muted'светлыйприглушённая палитра на тёплом фоне
'mono'светлыйодин тон от светлого к тёмному — для этапов и уровней, не для разных категорий
'contrast'светлыйвсе цвета серий держат 3:1, включены тики, сплошная сетка, толстые линии
'midnight'тёмныйтёмная тема с синим отливом, палитра подобрана под более тёмный фон

'default' — светлая (используется без theme):

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Light theme (default)' },
    series: [
      { type: 'bar', xField: 'month', yField: 'desktop', name: 'Desktop' },
      { type: 'line', xField: 'month', yField: 'mobile', name: 'Mobile' },
    ],
    theme: 'default',
  };
}
ts
export function getData() {
  return [
    { month: 'Jan', desktop: 42, mobile: 28 },
    { month: 'Feb', desktop: 49, mobile: 34 },
    { month: 'Mar', desktop: 46, mobile: 41 },
    { month: 'Apr', desktop: 58, mobile: 47 },
    { month: 'May', desktop: 63, mobile: 55 },
    { month: 'Jun', desktop: 60, mobile: 62 },
  ];
}

'dark' — тёмная:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Dark theme (dark)' },
    series: [
      { type: 'bar', xField: 'month', yField: 'desktop', name: 'Desktop' },
      { type: 'line', xField: 'month', yField: 'mobile', name: 'Mobile' },
    ],
    theme: 'dark',
  };
}
ts
export function getData() {
  return [
    { month: 'Jan', desktop: 42, mobile: 28 },
    { month: 'Feb', desktop: 49, mobile: 34 },
    { month: 'Mar', desktop: 46, mobile: 41 },
    { month: 'Apr', desktop: 58, mobile: 47 },
    { month: 'May', desktop: 63, mobile: 55 },
    { month: 'Jun', desktop: 60, mobile: 62 },
  ];
}

'contrast' — тема с упором на доступность: меняет не только цвета, но и вид осей:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'High-contrast preset' },
    series: [
      { type: 'bar', xField: 'quarter', yField: 'product', name: 'Product' },
      { type: 'line', xField: 'quarter', yField: 'service', name: 'Service' },
    ],
    // the preset inks up the chrome on its own: ticks on, solid grid, thicker lines
    theme: 'contrast',
  };
}
ts
export function getData() {
  return [
    { quarter: 'Q1', product: 46, service: 28 },
    { quarter: 'Q2', product: 52, service: 34 },
    { quarter: 'Q3', product: 49, service: 45 },
    { quarter: 'Q4', product: 61, service: 52 },
  ];
}

Тему можно менять на лету — chart.updateDelta({ theme: 'dark' }) перерисует чарт с анимацией. Демки на этом сайте так и переключаются вместе с темой страницы (если пример не задаёт тему явно).

Палитры и цветовое зрение

'vibrant', 'contrast' и 'midnight' проверены симуляцией цветового зрения: соседние цвета серий не сливаются при протанопии и дейтеранопии. 'muted' ближе к границе — подходит для четырёх серий или вместе с подписями значений. Палитра 'default' и 'dark' появилась раньше этой проверки и сохраняет опубликованные цвета ради совместимости.

Кастомная тема

Объект темы: baseTheme (основа) + palette (цвета серий по кругу) + params (дизайн-токены) + axis (оформление осей):

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Sales funnel' },
    series: [{ type: 'bar', xField: 'stage', yField: 'count', name: 'Deals', cornerRadius: 6 }],
    legend: { enabled: false },
    // custom theme: palette + design tokens on top of a base theme
    theme: {
      baseTheme: 'dark',
      palette: { fills: ['#27c08d'] },
      params: {
        backgroundColor: '#0d1f1a',
        foregroundColor: '#d8f3e9',
        fontFamily: 'Georgia, serif',
      },
    },
  };
}
ts
export function getData() {
  return [
    { stage: 'Leads', count: 1840 },
    { stage: 'Qualified', count: 1120 },
    { stage: 'Demo', count: 640 },
    { stage: 'Contract', count: 310 },
    { stage: 'Payment', count: 245 },
  ];
}

Цвет, заданный в серии (fill, stroke), имеет приоритет над палитрой темы.

Дизайн-токены

В params по одному значению на токен — оно применяется сразу ко всем типам серий:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Design tokens' },
    subtitle: { text: 'one theme, every mark follows' },
    series: [
      { type: 'bar', xField: 'month', yField: 'north', name: 'North' },
      { type: 'line', xField: 'month', yField: 'south', name: 'South', marker: { enabled: true } },
    ],
    theme: {
      baseTheme: 'vibrant',
      params: {
        // one value each, applied across every series type at once
        fontSize: 12,
        strokeWidth: 3,
        cornerRadius: 6,
        fillOpacity: 0.5,
      },
      axis: { tick: true, gridDash: [] },
    },
  };
}
ts
export function getData() {
  return [
    { month: 'Jan', north: 34, south: 22 },
    { month: 'Feb', north: 41, south: 27 },
    { month: 'Mar', north: 38, south: 35 },
    { month: 'Apr', north: 52, south: 39 },
    { month: 'May', north: 57, south: 48 },
    { month: 'Jun', north: 54, south: 56 },
  ];
}

Три токена ведут себя иначе остальных. cornerRadius и fillOpacity по умолчанию не заданы: встроенные значения различаются намеренно — столбец прямоугольный, а range-bar скруглённый, area заливается с 0.35, а маркер с 0.85. Не задавайте их — каждая марка сохранит своё значение; задайте — они перекроют все сразу.

fontSizeбазовый кегль, по умолчанию 11. Остальные подписи отсчитываются от него фиксированным смещением: подписи осей — на базе, легенда и заголовки осей на шаг выше, заголовок чарта — на шесть. Смена базы двигает всю шкалу, сохраняя иерархию.

Веб-шрифты

Текст на canvas сам по себе загрузку шрифта не запускает: ещё не скачанный @font-face нарисуется — и измерится — запасным начертанием. Поэтому чарт сам запрашивает у браузера все семейства, упомянутые в опциях, и, когда настоящие начертания приходят, заново считает раскладку и перерисовывается: подписи, оси и легенда получают размеры того шрифта, который вы указали.

Шрифты, объявленные страницей позже — ленивый CSS-чанк, ваш собственный вызов document.fonts.add(), — тоже учитываются: чарт слушает их загрузку и перерисовывается, когда приходит одно из его семейств.

Поэтому смена fontFamily на ещё не загруженный шрифт даёт два кадра. chart.waitForUpdate() резолвится после второго — дождитесь его перед getImageDataURL(), если экспортируете картинку.

Если нужен ровно один кадр, поведение отключается:

js
{
  fonts: { autoReload: false },
}

Тогда чарт рисует тем начертанием, которое у браузера уже есть, и недостающие не запрашивает: с ещё не загруженным семейством он так и останется на запасном — текст на canvas сам загрузку шрифта не инициирует.

Оформление осей

ChartOptions.axes — массив, и overrides до него не дотягивается: для этого есть блок axis. В нём сразу переключатели, размеры и цвета всех осей:

ts
theme: {
  baseTheme: 'default',
  axis: {
    tick: true,
    tickSize: 4,
    gridDash: [],
    gridColor: '#eceff3',
    labelSize: 12,
    titleColor: '#1f2733',
  },
}

Три переключателя работают как общий выключатель: выключенный гасит элемент везде, включённый оставляет обычное правило (сетка на оси значений, линия на оси категорий). Чтобы включить сетку там, где правило её убрало, задайте её на самой оси.

Цвета — необязательное уточнение. Не трогаете color, gridColor и tickColor — все трое идут за params.axisColor; не трогаете labelColor — он идёт за params.mutedColor, titleColor за params.foregroundColor. Задали один — изменится только он.

Легенда и тултип

Это обычные блоки ChartOptions, поэтому тема достаёт до них через overrides.common — отдельных токенов для них нет, потому что два пути к одному пикселю хуже одного:

ts
theme: {
  baseTheme: 'dark',
  overrides: {
    common: {
      legend: { position: 'right', item: { label: { fontSize: 13 } }, background: { fill: '#1b1f27', cornerRadius: 8 } },
      tooltip: { background: '#11151c', borderColor: '#2b313b', borderRadius: 10 },
    },
  },
}

Так доступно всё из LegendOptions и TooltipOptions, а чарт, задавший ту же опцию сам, по-прежнему выигрывает.

Overrides

overrides — частичные options, вклеиваемые под пользовательские: common — chart-блоки для всех чартов, <seriesType>.series — дефолты серий данного типа. Это способ дотянуться до всего, что токены выразить не могут: стилей отдельного типа серий и нестилевых опций вроде legend.position.

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Overrides: defaults per series type' },
    series: [
      { type: 'bar', xField: 'month', yField: 'desktop', name: 'Desktop' },
      { type: 'line', xField: 'month', yField: 'mobile', name: 'Mobile' },
    ],
    theme: {
      overrides: {
        common: { legend: { position: 'right' } },
        bar: { series: { cornerRadius: 8, fillOpacity: 0.8 } },
        line: { series: { strokeWidth: 3.5, lineDash: [12, 6], marker: { enabled: false } } },
      },
    },
  };
}
ts
export function getData() {
  return [
    { month: 'Jan', desktop: 42, mobile: 28 },
    { month: 'Feb', desktop: 49, mobile: 34 },
    { month: 'Mar', desktop: 46, mobile: 41 },
    { month: 'Apr', desktop: 58, mobile: 47 },
    { month: 'May', desktop: 63, mobile: 55 },
    { month: 'Jun', desktop: 60, mobile: 62 },
  ];
}

Приоритет: дефолты библиотеки < токены темы < overrides.common < overrides[type].series < явные options. Токены ниже overrides, потому что overrides вмерживаются в options ещё до того, как рендер обратится к теме.

Опции ThemeOptions

ОпцияТипОписание
baseThemeThemeNameбазовая тема-основа
palette.fillsColorValue[]цвета заливки серий, по индексу серии
palette.strokesColorValue[]цвета обводки (по умолчанию = fills)
palette.sequentialColorValue[]шкала для серий с colorField и градиентной легенды
params.backgroundColorColorValueфон чарта
params.foregroundColorColorValueосновной цвет текста
params.mutedColorColorValueвторичный текст: подписи осей, подзаголовок, значения легенды
params.axisColorColorValueлинии осей, тики и сетка
params.fontFamilystringшрифт всех надписей
params.fontSizePixelsбазовый кегль (11), остальные размеры двигаются вместе с ним
params.strokeWidthPixelsтолщина линий данных — line, area и radar
params.lineDashPixels[]штрих линий данных; [] — сплошные
params.markStrokeWidthPixelsтолщина обводки заливок — столбцов, секторов, боксов
params.cornerRadiusPixelsскругление всех прямоугольных марок; не задан — у каждой своё
params.fillOpacityFractionпрозрачность всех заливок; не задан — у каждой марки своя
params.positiveColorColorValueрост: candlestick, ohlc
params.negativeColorColorValueпадение: candlestick, ohlc, отрицательные столбцы waterfall
axis.linebooleanлиния оси
axis.tickbooleanзасечки
axis.gridLinebooleanлинии сетки (и полярная паутина)
axis.strokeWidthPixelsтолщина линии оси, засечек и сетки
axis.gridDashPixels[]штрих сетки; [] — сплошная линия
axis.lineDashPixels[]штрих самой линии оси; по умолчанию сплошная
axis.colorColorValueтолько линия оси; по умолчанию params.axisColor
axis.gridColorColorValueтолько сетка; по умолчанию params.axisColor
axis.tickColorColorValueтолько засечки; по умолчанию params.axisColor
axis.tickSizePixelsдлина засечки (6)
axis.labelColorColorValueподписи делений; по умолчанию params.mutedColor
axis.labelSizePixelsкегль подписей; по умолчанию params.fontSize
axis.labelSpacingPixelsзазор между линией оси и подписями (8)
axis.titleColorColorValueзаголовок оси; по умолчанию params.foregroundColor
axis.titleSizePixelsкегль заголовка оси; на шаг выше params.fontSize
overrides.commonRecord<string, unknown>chart-блоки для всех чартов
overrides.<seriesType>.seriesRecord<string, unknown>дефолты серий конкретного типа