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 Данные графика X NorthPay PayLane Globex Pay Fees 6 9 5 Payout speed 9 6 7 Coverage 7 5 10 API quality 9 7 6 Fraud tools 8 6 9 Support 6 8 7 - 1. NorthPay45 / 60
- 2. Globex Pay44 / 60
- 3. PayLane41 / 60
Кандидат против рамки роли
Требование нарисовано контуром, а не фигурой:
dashи прозрачныйfill-colorу серии превращают его в рамку, против которой читается кандидат. Где закрашенная фигура уходит внутрь рамки — там собеседование нашло пробел, а список рядом с графиком говорит, насколько.Dana Whitfield · Senior backend engineerInterview loop, 2 October · levels 1–5 Данные графика X Role Dana Whitfield System design 4 5 Go 4 3 SQL 3 4 On-call 3 2 Mentoring 3 4 - 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 Данные графика Ось Lisbon Network average Максимум оси Orders 142 118 200 Basket 38 44 60 NPS 61 48 100 Repeat 34 29 50 Rating 4,7 4,3 5 Metric Lisbon Network Orders a day 142 118 Average basket, € 38 44 NPS 61 48 Repeat customers, % 34 29 Rating, of 5 4.7 4.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 Данные графика X September August Mon 412 356 Tue 368 342 Wed 351 330 Thu 340 335 Fri 296 301 Sat 164 102 Sun 141 88 Hover a day, or focus the chart and use the arrow keys.
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
fill | boolean | undefined | undefined | Заливать многоугольник серии; по умолчанию `true`. Без заливки остаётся контур — удобнее, когда серий много. |
height | number | undefined | undefined | Высота холста в пикселях; по умолчанию 280. Данные, загрузка и пустое состояние занимают её одинаково. |
width | number | undefined | 640 | Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. |
yDomain | readonly [number | null, number | null] | undefined | undefined | Границы оси значений при `shared`. Ноль в домене остаётся всегда. |
valueFormat | GrChartNumberFormat | undefined | undefined | Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители. |
showPoints | "auto" | "never" | "always" | undefined | undefined | Маркеры вершин: `auto` (по умолчанию), `always` или `never`. |
showLegend | boolean | "auto" | undefined | undefined | Легенда серий: `auto` (по умолчанию) — когда серий больше одной; `true` и `false` — всегда и никогда. |
legendPosition | "bottom" | "top" | undefined | undefined | Где стоит легенда: `bottom` (по умолчанию) или `top`. |
tooltip | boolean | undefined | undefined | Панель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен. |
activeIndexv-model | number | null | undefined | undefined | Курсор — `v-model:activeIndex`. Индекс оси. |
loading | boolean | undefined | false | Загрузка: на месте области построения — скелет той же высоты, место под оси уже зарезервировано. |
empty | boolean | undefined | undefined | Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. |
emptyText | string | undefined | undefined | Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. |
dataTable | "hidden" | "visible" | "off" | undefined | undefined | Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком, `off` — без неё. |
interactive | boolean | undefined | true | `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. |
size | "md" | "xs" | "sm" | "lg" | undefined | undefined | Кегль имён осей, колец и легенды по контрольной шкале ядра; по умолчанию — размер из `GrConfigProvider`. |
locale | string | undefined | undefined | BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. |
ariaLabel | string | undefined | undefined | Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём данных. |
ariaDescription | string | undefined | undefined | Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её. |
dataTableMaxRows | number | "auto" | undefined | undefined | Потолок строк скрытой таблицы данных. `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок, `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком — что из этого нужно, решает приложение. |
shape | "polygon" | "circle" | undefined | undefined | Форма колец сетки. |
startAngle | number | undefined | 0 | Угол первой оси в градусах от двенадцати часов, по часовой. |
axisScale | "shared" | "per-axis" | undefined | undefined | Как масштабировать спицы. `shared` — одна шкала на все оси: площадь фигуры сравнима, форма честна. `per-axis` — каждая ось нормирована своим максимумом; это единственный способ показать разнородные метрики на одной паутине, но площади при этом сравнивать нельзя. |
axisMax | Record<string, number> | undefined | undefined | Верхние границы осей при `per-axis`, ключ — имя оси. Без них максимум берётся из данных. |
rings | number | undefined | undefined | Желаемое число колец сетки — то же, что число делений оси значений. Фактическое следует лестнице «красивых» чисел и может отличаться: кольцо обязано стоять на круглом значении, иначе подпись под ним читается как случайная. При нормировке на ось круглых значений нет, и колец ровно столько, сколько запрошено. |
seriesобязательный | readonly GrChartSeries[] | readonly (number | null)[] | — | Серии по общему набору осей. Ось X всегда категориальная — своего `xScale` у радара нет. |
Слоты
| Slot | Type | Описание |
|---|---|---|
tooltip | { active: GrChartActivePoint; formatValue: (value: number | null) => string; } | Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота. |
legend | { series: readonly NormalizedSeries[]; toggle: (id: string) => void; } | Своя легенда вместо стандартной. У пустого графика не рисуется. |
empty | any | Своя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`. |
header | any | Заголовок над графиком, внутри его корня — выше легенды, стоящей сверху. |
События
| 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 | Сигнатура | Описание |
|---|---|---|
element | HTMLElement | null | Корневой элемент — для замеров и скролла в потребителе. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrChartRadarConfigurableProps, GrChartSize, GrChartRadarProps } from '@feugene/granularity-charts/components/GrChartRadar'Файлdist/types/components/GrChartRadar/config.d.ts
group тот же, что у остальных графиков: рама у них общая, и её чанк тоже.
Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер
превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося
компонента он входит, и классы рамы сборка извлекает оттуда сама.
GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их
**рендерит**: без объявления они не попадут в замыкание селекции, и
приложение не подмешает их CSS.
const grChartRadarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartRadar">;Файлdist/types/components/GrChartRadar/defaults.d.ts
Пропы 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
Классы общей рамы графика.
Рама — не компонент: у директории нет ни 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
const grChartRadarSafelist: string[];Файлdist/types/components/GrChartRadar/GrChartRadar.vue.d.ts
Лепестковая диаграмма: профиль по нескольким осям и сравнение профилей. Отвечает на вопрос, которого не закрывает ни один другой тип: не «сколько» и не «куда движется», а «какой формы». Столбцы дают те же числа и не дают формы; круг говорит о долях одного целого, а не о профиле. Третья полярная система координат в пакете — и она въезжает в общую раму меньшим числом подмен, чем первая: своими остаются только попадание, якорь тултипа и (в режиме нормировки на ось) объявление с таблицей.
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';
}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,…» соврал бы о числе.
Подписи колец стоят на биссектрисе между первой и второй спицей, а не на первой: там внешнее кольцо попадало в одну колонку с именем верхней оси.
Оси выравниваются до нормализации
Число спиц обязано быть постоянным. У декартовых графиков позиции считаются по видимым сериям, и это верно; на полярных ломает рисунок — спрячь легендой единственную серию, знавшую про ось, и паутина провернётся. Поэтому каждая серия дополняется пропусками до общего набора осей, а пропуск позицию удерживает.
Разрыв рвёт контур и снимает заливку этой серии целиком: замкнуть контур через пропуск значит нарисовать ребро, которого нет, а залить рваный контур — площадь, которой нет.
Пропы этого примера