GrChartRadar

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

Берут, когда форма профиля по нескольким метрикам.

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

Обзор

Когда брать

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

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

НужноБерите
Сравнить конкретные величины между собойGrChartBar
Показать, из чего состоит целоеGrChartPie
Показать ход значения во времениGrChartLine
Показать одно значение с подписьюGrStatistic

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

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

  • Три провайдера, шесть критериев, у каждого своя форма

    Каждая спица — оценка из 10, поэтому общая шкала честна и площади сравнимы: форма говорит, что NorthPay — выбор за API, а Globex — за покрытие, ещё до чтения чисел. fill делает каждый профиль фигурой, а v-model:hidden-series позволяет легенде убрать провайдера и из рейтинга рядом с графиком.

    Payment provider shortlistReview panel scores out of 10 · toggle providers in the legend
    Данные графика
    XNorthPayPayLaneGlobex Pay
    Fees695
    Payout speed967
    Coverage7510
    API quality976
    Fraud tools869
    Support687
    1. 1. NorthPay45 / 60
    2. 2. Globex Pay44 / 60
    3. 3. PayLane41 / 60
  • Кандидат против рамки роли

    Требование нарисовано контуром, а не фигурой: dash и прозрачный fill-color у серии превращают его в рамку, против которой читается кандидат. Где закрашенная фигура уходит внутрь рамки — там собеседование нашло пробел, а список рядом с графиком говорит, насколько.

    Dana Whitfield · Senior backend engineerInterview loop, 2 October · levels 1–5
    Данные графика
    XRoleDana Whitfield
    System design45
    Go43
    SQL34
    On-call32
    Mentoring34
    • System design5 / 4
    • Go3 / 4
    • SQL4 / 3
    • On-call2 / 3
    • Mentoring4 / 3
  • Пять метрик, пять единиц

    axis-scale="per-axis" нормирует каждую спицу, а axis-max задаёт её потолок: на общей шкале 142 заказа вдавили бы рейтинг 4,7 в центр. Цена — сравнивается только форма, не площадь, поэтому настоящие значения стоят в таблице рядом, а ещё в тултипе и скрытой таблице.

    Lisbon store against the networkSeptember · each spoke has its own scale
    Данные графика
    ОсьLisbonNetwork averageМаксимум оси
    Orders142118200
    Basket384460
    NPS6148100
    Repeat342950
    Rating4,74,35
    MetricLisbonNetwork
    Orders a day142118
    Average basket, €3844
    NPS6148
    Repeat customers, %3429
    Rating, of 54.74.3
  • Неделя, которая замыкается сама на себя

    shape="circle" рисует кольца — это к лицу циклу, где воскресенье встречается с понедельником; start-angle ставит понедельник на двенадцать часов. v-model:active-index — спица под курсором или под стрелками, и её читает панель рядом с графиком.

    Support week, chats a dayWeekends grew by half since August · hover a day or use the arrow keys
    Данные графика
    XSeptemberAugust
    Mon412356
    Tue368342
    Wed351330
    Thu340335
    Fri296301
    Sat164102
    Sun14188

    Hover a day, or focus the chart and use the arrow keys.

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

Props

PropTypeпо умолчаниюОписание
fillboolean | undefinedundefinedЗаливать многоугольник серии; по умолчанию `true`. Без заливки остаётся контур — удобнее, когда серий много.
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 280. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
yDomainreadonly [number | null, number | null] | undefinedundefinedГраницы оси значений при `shared`. Ноль в домене остаётся всегда.
valueFormatGrChartNumberFormat | undefinedundefinedФормат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
showPoints"auto" | "never" | "always" | undefinedundefinedМаркеры вершин: `auto` (по умолчанию), `always` или `never`.
showLegendboolean | "auto" | undefinedundefinedЛегенда серий: `auto` (по умолчанию) — когда серий больше одной; `true` и `false` — всегда и никогда.
legendPosition"bottom" | "top" | undefinedundefinedГде стоит легенда: `bottom` (по умолчанию) или `top`.
tooltipboolean | undefinedundefinedПанель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен.
hiddenSeriesv-modelreadonly string[] | undefinedundefinedСкрытые серии по id — `v-model:hiddenSeries`. Оси при этом не исчезают.
activeIndexv-modelnumber | null | undefinedundefinedКурсор — `v-model:activeIndex`. Индекс оси.
loadingboolean | undefinedfalseЗагрузка: на месте области построения — скелет той же высоты, место под оси уже зарезервировано.
emptyboolean | undefinedundefinedПустое состояние явно. Не задано — график пуст, когда рисовать нечего.
emptyTextstring | undefinedundefinedТекст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета.

Слоты

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

События

EventСигнатураОписание
update:hiddenSeries(event: "update:hiddenSeries", value: string[]): void—
update:activeIndex(event: "update:activeIndex", value: number | null): void—
pointClick(event: "pointClick", value: GrChartActivePoint): void—
pointHover(event: "pointHover", value: GrChartActivePoint | null): void—
legendToggle(event: "legendToggle", value: { seriesId: string; hidden: boolean; }): void—

Методы

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

Типы

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

Импорт
import type { GrChartRadarConfigurableProps, GrChartSize, GrChartRadarProps } from '@feugene/granularity-charts/components/GrChartRadar'

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

grChartRadarConfigconst

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

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

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

GrChartRadarConfigurablePropsinterface

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

interface GrChartRadarConfigurableProps {
    size: GrChartSize;
    height: number;
    axisScale: 'shared' | 'per-axis';
    /** Число колец сетки. У радара это то же, что число делений оси значений. */
    rings: number;
    fill: boolean;
    shape: 'polygon' | 'circle';
    showPoints: 'auto' | 'always' | 'never';
    showLegend: boolean | 'auto';
    legendPosition: 'top' | 'bottom';
    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/GrChartRadar/safelist.d.ts

grChartRadarSafelistconst
const grChartRadarSafelist: string[];

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

GrChartRadarPropsinterface

Лепестковая диаграмма: профиль по нескольким осям и сравнение профилей. Отвечает на вопрос, которого не закрывает ни один другой тип: не «сколько» и не «куда движется», а «какой формы». Столбцы дают те же числа и не дают формы; круг говорит о долях одного целого, а не о профиле. Третья полярная система координат в пакете — и она въезжает в общую раму меньшим числом подмен, чем первая: своими остаются только попадание, якорь тултипа и (в режиме нормировки на ось) объявление с таблицей.

interface GrChartRadarProps {
    /** Серии по общему набору осей. Ось X всегда категориальная — своего `xScale` у радара нет. */
    series: readonly GrChartSeries[] | readonly (number | null)[];
    /**
     * Как масштабировать спицы.
     *
     * `shared` — одна шкала на все оси: площадь фигуры сравнима, форма честна.
     * `per-axis` — каждая ось нормирована своим максимумом; это единственный
     * способ показать разнородные метрики на одной паутине, но площади при этом
     * сравнивать нельзя.
     */
    axisScale?: 'shared' | 'per-axis';
    /** Верхние границы осей при `per-axis`, ключ — имя оси. Без них максимум берётся из данных. */
    axisMax?: Record<string, number>;
    /**
     * Желаемое число колец сетки — то же, что число делений оси значений.
     *
     * Фактическое следует лестнице «красивых» чисел и может отличаться: кольцо
     * обязано стоять на круглом значении, иначе подпись под ним читается как
     * случайная. При нормировке на ось круглых значений нет, и колец ровно
     * столько, сколько запрошено.
     */
    rings?: number;
    /** Форма колец сетки. */
    shape?: 'polygon' | 'circle';
    /** Угол первой оси в градусах от двенадцати часов, по часовой. */
    startAngle?: number;
    /**
     * Заливать многоугольник серии; по умолчанию `true`. Без заливки остаётся контур — удобнее, когда
     * серий много.
     */
    fill?: boolean;
    /** Маркеры вершин: `auto` (по умолчанию), `always` или `never`. */
    showPoints?: 'auto' | 'always' | 'never';
    /**
     * Высота холста в пикселях; по умолчанию 280. Данные, загрузка и пустое состояние занимают её
     * одинаково.
     */
    height?: number;
    /** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
    width?: number;
    /** Границы оси значений при `shared`. Ноль в домене остаётся всегда. */
    yDomain?: readonly [number | null, number | null];
    /**
     * Легенда серий: `auto` (по умолчанию) — когда серий больше одной; `true` и `false` — всегда и
     * никогда.
     */
    showLegend?: boolean | 'auto';
    /** Где стоит легенда: `bottom` (по умолчанию) или `top`. */
    legendPosition?: 'top' | 'bottom';
    /**
     * Панель со значениями под курсором; по умолчанию `true`. `false` прячет только
     * панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают,
     * пока график интерактивен.
     */
    tooltip?: boolean;
    /** Скрытые серии по id — `v-model:hiddenSeries`. Оси при этом не исчезают. */
    hiddenSeries?: readonly string[];
    /** Курсор — `v-model:activeIndex`. Индекс оси. */
    activeIndex?: number | null;
    /**
     * Загрузка: на месте области построения — скелет той же высоты, место под оси уже
     * зарезервировано.
     */
    loading?: boolean;
    /** Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. */
    empty?: boolean;
    /** Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. */
    emptyText?: string;
    /**
     * Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком,
     * `off` — без неё.
     */
    dataTable?: 'hidden' | 'visible' | 'off';
    /** `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. */
    interactive?: boolean;
    /**
     * Кегль имён осей, колец и легенды по контрольной шкале ядра;
     * по умолчанию — размер из `GrConfigProvider`.
     */
    size?: GrChartSize;
    /**
     * Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
     */
    valueFormat?: GrChartNumberFormat;
    /** BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. */
    locale?: string;
    /**
     * Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём
     * данных.
     */
    ariaLabel?: string;
    /**
     * Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет
     * её.
     */
    ariaDescription?: string;
    /**
     * Потолок строк скрытой таблицы данных.
     *
     * `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше
     * потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а
     * поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок,
     * `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком —
     * что из этого нужно, решает приложение.
     */
    dataTableMaxRows?: number | 'auto';
}
GrChartRadarEmitsinterface
interface GrChartRadarEmits {
    /**
     * Легенда просит скрыть или показать серию — новый список id; применяет его потребитель
     * (`v-model:hiddenSeries`).
     */
    (e: 'update:hiddenSeries', value: string[]): void;
    /** Курсор сдвинулся: индекс позиции или `null` — для `v-model:activeIndex`. */
    (e: 'update:activeIndex', value: number | null): void;
    /** Щелчок или Enter по активной позиции — со значениями всех видимых серий в ней. */
    (e: 'pointClick', value: GrChartActivePoint): void;
    /** Курсор пришёл на позицию или ушёл с графика (`null`). */
    (e: 'pointHover', value: GrChartActivePoint | null): void;
    /** Нажат пункт легенды: какая серия и в какое состояние просится. */
    (e: 'legendToggle', value: {
        seriesId: string;
        hidden: boolean;
    }): void;
}

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

Две шкалы радиуса — и обе с ценой
РежимДоменЧем платим
shared (по умолчанию)один на все осиразнородные метрики свести нельзя: та, что крупнее, займёт шкалу целиком
per-axisсвой на каждую осьплощади фигур сравнивать нельзя — сравнивается только форма

Доля живёт только в расчёте радиуса. Собственное значение точки остаётся исходным во всех режимах: протеки нормировка в данные — тултип и скрытая таблица начали бы показывать проценты вместо величин.

При per-axis у кольца нет единого числа, поэтому потолок каждой оси стоит второй, мелкой строкой под её именем (max 200); целиком, одной строкой «Orders a day · 200», он остаётся в <title> подписи, тултипе и таблице.

Подписи уступают паутине, а не наоборот

Радиус — наибольший, при котором каждая подпись помещается в холст с той стороны, куда смотрит её ось, но не меньше половины от возможного: в узкой карточке подписи уступают место рисунку, а не съедают его. Имя шире четверти холста переносится по пробелу на две строки; не помещается и так — кончается многоточием с полным текстом в <title>. Потолок оси при per-axis не усекается никогда: «max 5,…» соврал бы о числе.

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

Оси выравниваются до нормализации

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

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