GrChartHeatmap

  • charts
  • Графики
  • API · 28 props

Берут, когда матрица удержания по когортам.

import { GrChartHeatmap } from '@feugene/granularity-charts/components/GrChartHeatmap'

Обзор

Когда брать

  • матрица удержания по когортам — строки когорты, колонки смещения: сразу видно, на каком месяце кривая выполаживается;
  • два категориальных измерения и одна величина — часы × дни недели, сервис × версия, регион × канал;
  • важна форма, а не точные числа — пятно и градиент читаются быстрее двадцати ячеек с цифрами;
  • матрица разрежена — null оставляет ячейку пустой, и «ещё не наступило» видно как отсутствие, а не как ноль;
  • отклонение от нормы в обе стороны — расходящаяся шкала красит недобор и перебор разными ролями вокруг середины.

Когда взять другое

НужноБерите
Сравнить величины одного измеренияGrChartBar
Показать ход величины во времениGrChartLine
Показать точные числа, которые будут читать и сортироватьGrDataTable
Показать профиль по нескольким осямGrChartRadar

Примеры 4 у каждого — живое демо и его код

  • Удержание по месячным когортам

    Строки — когорты, колонки — месяцы с регистрации: глаз находит месяц, где кривая выполаживается, не сравнивая тридцать чисел. Ещё не наступившие месяцы — null: ячейка остаётся пустой, а не красится как нулевое удержание. steps квантует цвет, чтобы легенда читалась, слот tooltip добавляет размер когорты, а cell-click открывает когорту.

    Retention by monthly cohortShare still paying N months after signing up · click a cohort
    40100
    Данные графика
    СтрокаM0M1M2M3M4M5
    Apr1007162585554
    May10074666159—
    Jun100696057——
    Jul1007770———
    Aug10079————
    Sep100—————

    Click a cell to open its cohort.

  • Выше и ниже плана

    scale="diverging" с midpoint в нуле даёт недобору и перевыполнению разные роли, а «по плану» ставит в нейтральную середину. Последовательная шкала покрасила бы худший недобор тем же бледным тоном, что и малое перевыполнение. domain держит шкалу симметричной, а не начавшийся квартал — null.

    Revenue against planDeviation by region and quarter
    -1515
    Данные графика
    СтрокаQ1Q2Q3Q4
    North4910—
    South-6-3-8—
    East255—
    West-12-9-12—
    Central739—
  • Использование функций по тарифам — словами

    Четыре steps превращают доли в корзины, а слот legend называет их — «редко», «иногда», «часто», «почти все» — с образцами из colorAt, вместо голых порогов. high-color принимает любую роль темы.

    Feature adoption by planShare of accounts that used it in September
    Rare · 0–25% Some · 25–50% Many · 50–75% Most · 75–100%
    Данные графика
    СтрокаStarterTeamBusiness
    Recurring invoices386481
    Payment links527188
    Multi-currency62257
    API access21563
    Audit log0941
  • Когда клиенты пишут в поддержку

    Дни недели по часам, без чисел в ячейках — смысл в форме: выходные — холодная полоса, обед понедельника — самое горячее место. steps="0" оставляет шкалу непрерывной для плавной суточной кривой, а v-model:active-cell выводит ячейку под курсором или клавиатурой в строку над графиком.

    When customers write to supportPeak: Monday around 13:00
    553
    Данные графика
    Строка8:009:0010:0011:0012:0013:0014:0015:0016:0017:0018:0019:00
    Mon303743485153535148433730
    Tue212834394244444239342821
    Wed212834394244444239342821
    Thu212834394244444239342821
    Fri212834394244444239342821
    Sat578101111111110875
    Sun578101111111110875

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
valueFormatGrChartNumberFormat | undefinedundefinedФормат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
showLegendboolean | undefinedundefinedПолоса шкалы с границами домена; по умолчанию `true` — без неё цвет нечем прочитать.
tooltipboolean | undefinedundefinedПанель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен.
loadingboolean | undefinedfalseЗагрузка: на месте области построения — скелет той же высоты, место под оси уже зарезервировано.
emptyboolean | undefinedundefinedПустое состояние явно. Не задано — график пуст, когда рисовать нечего.
emptyTextstring | undefinedundefinedТекст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета.
dataTable"hidden" | "visible" | "off" | undefinedundefinedТаблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком, `off` — без неё.
interactiveboolean | undefinedtrue`false` — график становится картинкой: без фокуса, тултипа и клавиатуры.
size"md" | "xs" | "sm" | "lg" | undefinedundefinedКегль подписей строк, колонок и ячеек по контрольной шкале ядра; по умолчанию — размер из `GrConfigProvider`.
localestring | undefinedundefinedBCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`.
ariaLabelstring | undefinedundefinedДоступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём данных.
ariaDescriptionstring | undefinedundefinedОписание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её.

Слоты

SlotTypeОписание
tooltip{ active: GrChartActivePoint; cell: GrChartHeatmapCell | null; }Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота.
legend{ thresholds: readonly number[]; colorAt: (fraction: number) => string; }Своя легенда вместо стандартной. У пустого графика не рисуется.
emptyanyСвоя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`.
headeranyЗаголовок над графиком, внутри его корня — выше легенды, стоящей сверху.

События

EventСигнатураОписание
update:activeCell(event: "update:activeCell", value: { x: number; y: number; } | null): void—
cellClick(event: "cellClick", value: GrChartHeatmapCell): void—
cellHover(event: "cellHover", value: GrChartHeatmapCell | null): void—

Методы

MethodsСигнатураОписание
elementHTMLElement | nullКорневой элемент — для замеров и скролла в потребителе.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrChartHeatmapConfigurableProps, GrChartSize, GrChartHeatmapCell } from '@feugene/granularity-charts/components/GrChartHeatmap'

Файлdist/types/components/GrChartHeatmap/config.d.ts

grChartHeatmapConfigconst

group тот же, что у остальных графиков: рама у них общая, и её чанк тоже. Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося компонента он входит, и классы рамы сборка извлекает оттуда сама. GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их **рендерит**: без объявления они не попадут в замыкание селекции, и приложение не подмешает их CSS.

const grChartHeatmapConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartHeatmap">;

Файлdist/types/components/GrChartHeatmap/defaults.d.ts

GrChartHeatmapConfigurablePropsinterface

Пропы GrChartHeatmap, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrChartHeatmap: { … } }">. Только оформление: матрица, подписи и границы шкалы задаются на месте.

interface GrChartHeatmapConfigurableProps {
    size: GrChartSize;
    height: number;
    /** Зазор между ячейками в пикселях: он идёт в геометрию сетки, а не в CSS. */
    cellGap: number;
    /** Число ступеней шкалы; `0` — непрерывная. */
    steps: number;
    showLegend: boolean;
    showValues: boolean | 'auto';
    tooltip: boolean;
    dataTable: 'hidden' | 'visible' | 'off';
    /**
     * Потолок строк скрытой таблицы данных.
     *
     * Политика приложения: держать ли весь ряд в дереве доступности, усечь ли его
     * до читаемого или убрать таблицу вовсе — решение уровня продукта, а не
     * отдельного графика.
     */
    dataTableMaxRows: number | 'auto';
}

Файлdist/types/components/GrChartFrame/chartFrameStyles.d.ts

GrChartSizetype

Классы общей рамы графика. Рама — не компонент: у директории нет ни index.ts, ни config.ts, поэтому в реестры она не попадает (гейт frameOwnership.test.ts). Владельцем токенов --gr-chart-frame-* она при этом остаётся: гейт покомпонентных токенов ищет реестры по файлу tokens.json в любой директории Gr…, а не по составу компонента. Пиксельных литералов и утилит uno-шкалы (text-sm, rounded-md) здесь нет: темой они не настраиваются, а выглядят правильно.

type GrChartSize = GrComponentSize;

Файлdist/types/components/GrChartHeatmap/safelist.d.ts

grChartHeatmapSafelistconst
const grChartHeatmapSafelist: string[];

Файлdist/types/components/GrChartHeatmap/GrChartHeatmap.vue.d.ts

GrChartHeatmapCellinterface

Теплокарта: матрица значений, где цвет кодирует величину. Шкала — одна роль темы через color-mix, а не набор из пяти цветов: пять цветов пришлось бы подбирать заново под тёмную тему и заново же под вторую теплокарту на соседней странице. Обе оси здесь категориальные, поэтому осей рамы нет вовсе — подписи строк и колонок компонент рисует сам, как круг рисует свои выноски. Внутрь рамы уходит линейный ряд по индексам колонок: у категориальной оси подписи проходят через дедупликацию, и две одинаковые колонки («Q1» за два года) слиплись бы в одну позицию, после чего курсор адресовал бы чужую ячейку.

interface GrChartHeatmapCell {
    x: number;
    y: number;
    xLabel: string;
    yLabel: string;
    value: number | null;
}
GrChartHeatmapPropsinterface
interface GrChartHeatmapProps {
    /** Значения построчно: `values[y][x]`. `null` — ячейки нет, а не «ноль». */
    values: readonly (readonly (number | null)[])[];
    /**
     * Подписи колонок — по одной на колонку `values`. Не помещаются в ячейку — усекаются или
     * прореживаются.
     */
    xLabels: readonly string[];
    /** Подписи строк — по одной на строку `values`. Колонка под них — до 40% ширины. */
    yLabels: readonly string[];
    /** Границы шкалы. Не заданы — считаются по данным. */
    domain?: readonly [number, number];
    /** Последовательная (одна роль) или расходящаяся (две роли вокруг середины). */
    scale?: HeatmapScaleKind;
    /** Середина расходящейся шкалы. */
    midpoint?: number;
    /** Цвет нижнего края расходящейся шкалы; по умолчанию `--gr-danger`. */
    lowColor?: string;
    /**
     * Цвет верхнего края шкалы; у последовательной — единственный цвет. По умолчанию `--gr-chart-1`.
     */
    highColor?: string;
    /** Цвет середины расходящейся шкалы; по умолчанию `--gr-muted`. */
    midColor?: string;
    /** Число ступеней шкалы. `0` — непрерывная. */
    steps?: number;
    /** Зазор между ячейками в пикселях. */
    cellGap?: number;
    /** Полоса шкалы с границами домена; по умолчанию `true` — без неё цвет нечем прочитать. */
    showLegend?: boolean;
    /** Значения в ячейках. `'auto'` — только если они помещаются. */
    showValues?: boolean | 'auto';
    /**
     * Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
     */
    valueFormat?: GrChartNumberFormat;
    /**
     * Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её
     * одинаково.
     */
    height?: number;
    /** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
    width?: number;
    /**
     * Панель со значениями под курсором; по умолчанию `true`. `false` прячет только
     * панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают,
     * пока график интерактивен.
     */
    tooltip?: boolean;
    /** Курсор — `v-model:activeCell`. */
    activeCell?: {
        x: number;
        y: number;
    } | null;
    /**
     * Загрузка: на месте области построения — скелет той же высоты, место под оси уже
     * зарезервировано.
     */
    loading?: boolean;
    /** Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. */
    empty?: boolean;
    /** Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. */
    emptyText?: string;
    /**
     * Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком,
     * `off` — без неё.
     */
    dataTable?: 'hidden' | 'visible' | 'off';
    /** `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. */
    interactive?: boolean;
    /**
     * Кегль подписей строк, колонок и ячеек по контрольной шкале ядра; по умолчанию — размер из
     * `GrConfigProvider`.
     */
    size?: GrChartSize;
    /** BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. */
    locale?: string;
    /**
     * Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём
     * данных.
     */
    ariaLabel?: string;
    /**
     * Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет
     * её.
     */
    ariaDescription?: string;
    /**
     * Потолок строк скрытой таблицы данных.
     *
     * `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше
     * потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а
     * поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок,
     * `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком —
     * что из этого нужно, решает приложение.
     */
    dataTableMaxRows?: number | 'auto';
}
GrChartHeatmapEmitsinterface
interface GrChartHeatmapEmits {
    /** Курсор сдвинулся: ячейка `{ x, y }` или `null` — для `v-model:activeCell`. */
    (e: 'update:activeCell', value: {
        x: number;
        y: number;
    } | null): void;
    /** Щелчок или Enter по ячейке — с её значением и подписями строки и колонки. */
    (e: 'cellClick', value: GrChartHeatmapCell): void;
    /** Курсор пришёл на ячейку или ушёл с графика (`null`). */
    (e: 'cellHover', value: GrChartHeatmapCell | null): void;
}

Использование из прозы библиотеки

null — не ноль и не минимум шкалы

Отсутствующая ячейка не заливается вовсе, в скрытой таблице получает прочерк и в домен шкалы не входит. Для когорт это принципиально: «месяц ещё не наступил» и «удержание ноль процентов» — разные утверждения, и покрасить первое как второе значило бы нарисовать полное вымывание там, где данных просто нет.

Строки разной длины дополняются null справа, а не нулями: матрица когорт разрежена по построению.

Шкала — одна роль темы, а не пять цветов

Цвет ячейки считается color-mix по роли (highColor, а у расходящейся шкалы ещё lowColor и midColor). Пять подобранных цветов пришлось бы подбирать заново под тёмную тему и заново же — под вторую теплокарту на соседней странице; роль подстраивается сама.

steps квантует долю: 5 (по умолчанию) даёт пять ступеней, 0 — непрерывный градиент. Края шкалы в обоих режимах совпадают, различаются середины.

Расходящаяся шкала нормируется на больший из отступов от середины — так она симметрична по построению, а не по совпадению данных. У минимума при этом остаётся заметная примесь краски: без неё он был бы неотличим от пустой ячейки.

Легенда расшифровывает шкалу, а не перечисляет категории

Полоса с подписями границ домена. Без неё цвет нечем прочитать: у матрицы нет оси значений, и «насыщеннее» само по себе не значит ничего.

Клавиатура двумерная

←→ меняют колонку, ↑↓ — строку, Home/End уводят в край строки, PageUp/PageDown — в край колонки. Одна остановка Tab на всю карту.

Ни одна ось не кольцуется. Перескок с конца строки на начало следующей дезориентирует: читатель теряет, в какой он строке, а сказать ему об этом нечем — объявляется только содержимое ячейки.

Скрытая таблица здесь ценнее всего

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

Подписи строк и колонок не налезают

Колонка имён строк растёт по самому длинному имени — до 40% ширины графика, но не уже 96px; длиннее — многоточие и полное имя в <title>. Подписи колонок стоят по центру ячеек, пока помещаются. В узкой ячейке подпись усекается, пока от неё остаются хотя бы три знака, а дальше ряд прореживается — с первой и последней колонкой, — и каждая оставшаяся подпись занимает место до середины пути к соседям. Наложиться подписям не на чем, а имя любой колонки остаётся в тултипе и скрытой таблице.

Границы

Компонент не кластеризует, не сортирует строки и колонки и не рисует дендрограмму: порядок задаёт потребитель, потому что только он знает, что здесь означает «похожие». Неравномерной сетки тоже нет — ячейки одного размера, иначе площадь начала бы кодировать второй показатель, о котором читателю не сказали.