GrChartPie
Берут, когда из чего состоит одно целое.
Обзор
Когда брать
- из чего состоит одно целое — структура расходов, доли каналов, распределение статусов: круг показывает часть от целого и ничего больше;
- долей немного — три-шесть; дальше сегменты становятся тоньше подписи, и сравнить их глазом уже нельзя;
- в центре нужен итог —
variant: 'donut'освобождает середину под сумму или ключевое число (totalLabel, слот#center); - мелочь надо убрать с картинки —
labelMinShareуводит подписи долей ниже порога, оставляя их в легенде и в таблице данных.
Когда взять другое
| Нужно | Берите |
|---|---|
| Сравнить величины между собой | GrChartBar |
| Показать, как состав менялся во времени | GrChartArea со стеком |
| Показать долю одного показателя | GrProgressCircle |
| Сравнить форму профиля по нескольким осям | GrChartRadar |
Сравнивать доли между двумя кругами человек не умеет: угол читается хуже длины, а два круга ещё и разного размера. Два состава рядом — это столбцы, нормированные к ста процентам.
Примеры 4 у каждого — живое демо и его код
На что ушло хранилище
Полоса отвечает «насколько полно», бублик — «чем». Своя легенда графика выключена, потому что легенда — это список справа:
v-model:active-indexсвязывает долю и её строку, и наведение на любую подсвечивает обе.total-labelподписывает сумму в середине.Storage412 GB of 500 GBДанные диаграммы Категория Значение Доля Attachments 186 45 % Backups 124 30 % Exports 58 14 % Audit log 44 11 % - AttachmentsFiles on invoices and receipts186 GB
- BackupsNightly, kept for 30 days124 GB
- ExportsCSV and PDF reports58 GB
- Audit logKept for 2 years44 GB
Статусы в цветах своего смысла
colorу доли заменяет палитру ролью темы: зелёный, янтарный и красный говорят «вовремя, опоздала, не доставлена» раньше, чем прочитана легенда.labels="share"пишет проценты, а слот#centerставит в середину главное — «92% вовремя» вместо суммы — по переданным емуcx,cyи кеглям.Deliveries, last week92% arrived on time - On time1 842 · 92 %
- Late131 · 7 %
- Failed attempt27 · 1 %
Данные диаграммы Категория Значение Доля On time 1 842 92 % Late 131 7 % Failed attempt 27 1 % Доля, которая фильтрует очередь
Три части — предел, на котором круг ещё читается.
labels="value"пишет количества — «17 по телефону» это ответ, «18%» — намёк, — аslice-clickсужает список обращений под графиком до этого канала.Open tickets by channel - Email48 · 50 %
- Chat31 · 32 %
- Phone17 · 18 %
Данные диаграммы Категория Значение Доля Email 48 50 % Chat 31 32 % Phone 17 18 % - T-2291Invoice shows the old VAT number
- T-2294Cannot add a second admin
- T-2297Card declined on renewal
Длинный хвост в «Прочем»
Десять категорий стали бы щепками, которые никто не сравнит, поэтому страница сводит мелкие в «Прочее» и ставит его последним — график не сортирует и не группирует, потому что только страница знает, где место «Прочему».
label-min-shareоставляет тонкие доли без подписи на рисунке, а слотtooltipраскрывает «Прочее».Expenses, September€278.0k in total - Salaries182 · 65 %
- Cloud41 · 15 %
- Office23 · 8 %
- Marketing19 · 7 %
- Other13 · 5 %
Данные диаграммы Категория Значение Доля Salaries 182 65 % Cloud 41 15 % Office 23 8 % Marketing 19 7 % Other 13 5 %
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
height | number | undefined | undefined | Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково. |
width | number | undefined | 640 | Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. |
valueFormat | GrChartNumberFormat | undefined | undefined | Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители. |
showLegend | boolean | undefined | undefined | Легенда долей со значениями и процентами; по умолчанию `true`. |
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'` убирает таблицу целиком — что из этого нужно, решает приложение. |
labels | "none" | "value" | "share" | undefined | undefined | Подписи снаружи кольца. `share` — проценты, `value` — значения. |
variant | "pie" | "donut" | undefined | undefined | Форма: `pie` (по умолчанию) — круг, `donut` — кольцо с итогом в середине (слот `#center`). |
donutRatio | number | undefined | undefined | Радиус дырки бублика долей внешнего радиуса. |
startAngle | number | undefined | 0 | Угол первой доли в градусах от двенадцати часов, по часовой. |
labelMinShare | number | undefined | LABEL_MIN_SHARE | Наименьшая доля, которую подписывают: на трёх процентах подпись перекроет соседнюю. |
totalLabel | string | undefined | undefined | Подпись под итогом в середине бублика. |
dataобязательный | readonly (number | null)[] | readonly GrChartPieSlice[] | — | Доли либо голый ряд чисел — тогда подписью становится порядковый номер. |
Слоты
| Slot | Type | Описание |
|---|---|---|
center |
| Середина бублика. Содержимое — SVG в координатах холста: ставьте его от `cx`/`cy` и держите внутри `innerRadius`. |
tooltip |
| Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота. |
legend | { slices: readonly PieEntry[]; formatValue: (value: number | null) => string; } | Своя легенда вместо стандартной. У пустого графика не рисуется. |
empty | any | Своя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`. |
header | any | Заголовок над графиком, внутри его корня — выше легенды, стоящей сверху. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:activeIndex | (event: "update:activeIndex", value: number | null): void | — |
sliceClick | (event: "sliceClick", value: GrChartPieActiveSlice): void | — |
sliceHover | (event: "sliceHover", value: GrChartPieActiveSlice | null): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
element | HTMLElement | null | Корневой элемент — для замеров и скролла в потребителе. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrChartPieConfigurableProps, GrChartSize, GrChartPieTexture } from '@feugene/granularity-charts/components/GrChartPie'Файлdist/types/components/GrChartPie/config.d.ts
group тот же, что у остальных графиков: рама у них общая, и её чанк тоже.
Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер
превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося
компонента он входит, и классы рамы сборка извлекает оттуда сама.
GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их
**рендерит**: без объявления они не попадут в замыкание селекции, и
приложение не подмешает их CSS.
const grChartPieConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartPie">;Файлdist/types/components/GrChartPie/defaults.d.ts
Пропы GrChartPie, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrChartPie: { … } }">.
Только оформление: данные и подписи задаются на месте.
interface GrChartPieConfigurableProps {
size: GrChartSize;
height: number;
variant: 'pie' | 'donut';
donutRatio: number;
labels: 'none' | 'share' | 'value';
showLegend: boolean;
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/GrChartPie/grChartPieStyles.d.ts
Текстуры заливки — второй различитель поверх цвета.
Цветов в палитре пять; шестая доля повторила бы первую, а на круге соседние
доли стоят вплотную, и повтор читается как «это одно и то же». Период
текстуры разведён с периодом цвета — та же арифметика, что у seriesStyle
с формой точки, и тот же принцип: цвет никогда не единственный различитель.
const GR_CHART_PIE_TEXTURES: readonly ["solid", "hatch", "hatch-reverse", "grid"];type GrChartPieTexture = typeof GR_CHART_PIE_TEXTURES[number];function pieTextureFor(index: number): GrChartPieTexture;Штрихи текстуры для плитки со стороной tile.
Диагональ дублируется у обоих углов и вылезает за плитку на единицу: без
этого стык соседних плиток даёт разрыв штриха, и вместо ровной штриховки
получается пунктир по сетке. Метка легенды рисует те же штрихи, только по
своей стороне, — поэтому функция принимает размер, а не берёт его из
константы.
function pieTexturePaths(texture: GrChartPieTexture, tile?: number): string[];Файлdist/types/components/GrChartPie/safelist.d.ts
const grChartPieSafelist: string[];Файлdist/types/components/GrChartPie/GrChartPie.vue.d.ts
Круговая диаграмма: доли одного целого — сектором или кольцом. Живёт на общей раме, но система координат у неё полярная, и три вещи рама получает от круга: попадание указателя (угловое, а не по абсциссе), якорь тултипа (центроид доли) и строку скрытой таблицы (доля, а не значение по X). Осей и сетки у круга нет, вертикали под активной точкой — тоже.
interface GrChartPieSlice {
label: string;
/** `null`, ноль и отрицательное значение доли не дают: круг рисует только положительное. */
value: number | null;
/** Свой цвет вместо палитры. Заданный цвет отменяет текстуру: выбор потребителя сильнее. */
color?: string;
meta?: unknown;
}interface GrChartPieActiveSlice {
index: number;
label: string;
value: number | null;
/** Доля от суммы, 0…1. `null` — доля не нарисована. */
share: number | null;
color: string;
}interface GrChartPieProps {
/** Доли либо голый ряд чисел — тогда подписью становится порядковый номер. */
data: readonly GrChartPieSlice[] | readonly (number | null)[];
/** Форма: `pie` (по умолчанию) — круг, `donut` — кольцо с итогом в середине (слот `#center`). */
variant?: 'pie' | 'donut';
/** Радиус дырки бублика долей внешнего радиуса. */
donutRatio?: number;
/**
* Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её
* одинаково.
*/
height?: number;
/** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
width?: number;
/** Угол первой доли в градусах от двенадцати часов, по часовой. */
startAngle?: number;
/** Подписи снаружи кольца. `share` — проценты, `value` — значения. */
labels?: 'none' | 'share' | 'value';
/** Наименьшая доля, которую подписывают: на трёх процентах подпись перекроет соседнюю. */
labelMinShare?: number;
/** Легенда долей со значениями и процентами; по умолчанию `true`. */
showLegend?: boolean;
/** Где стоит легенда: `bottom` (по умолчанию) или `top`. */
legendPosition?: 'top' | 'bottom';
/**
* Панель со значениями под курсором; по умолчанию `true`. `false` прячет только
* панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают,
* пока график интерактивен.
*/
tooltip?: boolean;
/** Курсор — `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;
/** Подпись под итогом в середине бублика. */
totalLabel?: string;
/**
* Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём
* данных.
*/
ariaLabel?: string;
/**
* Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет
* её.
*/
ariaDescription?: string;
/**
* Потолок строк скрытой таблицы данных.
*
* `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше
* потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а
* поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок,
* `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком —
* что из этого нужно, решает приложение.
*/
dataTableMaxRows?: number | 'auto';
}interface GrChartPieEmits {
/** Курсор сдвинулся: индекс доли или `null` — для `v-model:activeIndex`. */
(e: 'update:activeIndex', value: number | null): void;
/** Щелчок или Enter по доле — с её значением и процентом. */
(e: 'sliceClick', value: GrChartPieActiveSlice): void;
/** Курсор пришёл на долю или ушёл с графика (`null`). */
(e: 'sliceHover', value: GrChartPieActiveSlice | null): void;
}Доступность
Паттерн APG
Смены серии нет — серия одна. Активная доля вырастает наружу
| Клавиша | Действие |
|---|---|
| — | доля |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Отрицательных долей не бывает
Отрицательные значения и пропуски отбрасываются, а не берутся по модулю: «минус
три» части круга не существует, и молча превратить её в плюс значило бы
нарисовать данные, которых нет. Отброшенное видно по sourceIndex доли — он
остаётся индексом входа.
Порядок долей — как пришли
Круг ничего не сортирует и не группирует. Хвост в «Прочее» собирает потребитель, потому что только он знает, что «Прочее» обязано стоять последним, а не попасть в середину по величине.
Своё число в середине бублика
По умолчанию в дырке стоит итог — сумма долей по valueFormat и подпись
totalLabel. Слот #center заменяет его своим содержимым. Оно рисуется
внутри холста, поэтому это SVG, а не HTML, и ставится от координат,
которые слот отдаёт: cx/cy — центр бублика, innerRadius — радиус дырки,
valueFont/labelFont — кегли содержимого по умолчанию, подобранные от её
размера.
<GrChartPie :data="deliveries" variant="donut">
<template #center="{ cx, cy, valueFont, labelFont }">
<text :x="cx" :y="cy - labelFont / 2" text-anchor="middle" dominant-baseline="middle" :font-size="valueFont" font-weight="600">
92%
</text>
<text :x="cx" :y="cy + valueFont * 0.55" text-anchor="middle" dominant-baseline="middle" :font-size="labelFont">
on time
</text>
</template>
</GrChartPie>Шире диаметра дырки (2 × innerRadius) содержимое ляжет на кольцо. Итог
слоту тоже приходит — total числом и formattedTotal строкой по
valueFormat.
Пропы этого примера