GrChartPie

  • charts
  • Графики
  • API · 24 props
  • ⌨ клавиши

Берут, когда из чего состоит одно целое.

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

Обзор

Когда брать

  • из чего состоит одно целое — структура расходов, доли каналов, распределение статусов: круг показывает часть от целого и ничего больше;
  • долей немного — три-шесть; дальше сегменты становятся тоньше подписи, и сравнить их глазом уже нельзя;
  • в центре нужен итог — variant: 'donut' освобождает середину под сумму или ключевое число (totalLabel, слот #center);
  • мелочь надо убрать с картинки — labelMinShare уводит подписи долей ниже порога, оставляя их в легенде и в таблице данных.

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

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

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

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

  • На что ушло хранилище

    Полоса отвечает «насколько полно», бублик — «чем». Своя легенда графика выключена, потому что легенда — это список справа: v-model:active-index связывает долю и её строку, и наведение на любую подсвечивает обе. total-label подписывает сумму в середине.

    Storage412 GB of 500 GB
    Данные диаграммы
    КатегорияЗначениеДоля
    Attachments18645 %
    Backups12430 %
    Exports5814 %
    Audit log4411 %
    • 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 time1 84292 %
    Late1317 %
    Failed attempt271 %
  • Доля, которая фильтрует очередь

    Три части — предел, на котором круг ещё читается. labels="value" пишет количества — «17 по телефону» это ответ, «18%» — намёк, — а slice-click сужает список обращений под графиком до этого канала.

    Open tickets by channel
    • Email48 · 50 %
    • Chat31 · 32 %
    • Phone17 · 18 %
    Данные диаграммы
    КатегорияЗначениеДоля
    Email4850 %
    Chat3132 %
    Phone1718 %
    • 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 %
    Данные диаграммы
    КатегорияЗначениеДоля
    Salaries18265 %
    Cloud4115 %
    Office238 %
    Marketing197 %
    Other135 %

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

Props

PropTypeпо умолчаниюОписание
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
valueFormatGrChartNumberFormat | undefinedundefinedФормат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
showLegendboolean | undefinedundefinedЛегенда долей со значениями и процентами; по умолчанию `true`.
legendPosition"bottom" | "top" | undefinedundefinedГде стоит легенда: `bottom` (по умолчанию) или `top`.
tooltipboolean | undefinedundefinedПанель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен.
activeIndexv-modelnumber | null | undefinedundefinedКурсор — `v-model:activeIndex`.
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`.

Слоты

SlotTypeОписание
center
{ total: number; formattedTotal: string; cx: number; cy: num{ total: number; formattedTotal: string; cx: number; cy: number; innerRadius: number; valueFont: number; labelFont: number; }
Середина бублика. Содержимое — SVG в координатах холста: ставьте его от `cx`/`cy` и держите внутри `innerRadius`.
tooltip
{ active: GrChartActivePoint; slice: GrChartPieActiveSlice |{ active: GrChartActivePoint; slice: GrChartPieActiveSlice | null; formatValue: (value: number | null) => string; }
Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота.
legend{ slices: readonly PieEntry[]; formatValue: (value: number | null) => string; }Своя легенда вместо стандартной. У пустого графика не рисуется.
emptyanyСвоя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`.
headeranyЗаголовок над графиком, внутри его корня — выше легенды, стоящей сверху.

События

EventСигнатураОписание
update:activeIndex(event: "update:activeIndex", value: number | null): void—
sliceClick(event: "sliceClick", value: GrChartPieActiveSlice): void—
sliceHover(event: "sliceHover", value: GrChartPieActiveSlice | null): void—

Методы

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

Типы

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

Импорт
import type { GrChartPieConfigurableProps, GrChartSize, GrChartPieTexture } from '@feugene/granularity-charts/components/GrChartPie'

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

grChartPieConfigconst

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

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

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

GrChartPieConfigurablePropsinterface

Пропы 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

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/GrChartPie/grChartPieStyles.d.ts

GR_CHART_PIE_TEXTURESconst

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

const GR_CHART_PIE_TEXTURES: readonly ["solid", "hatch", "hatch-reverse", "grid"];
GrChartPieTexturetype
type GrChartPieTexture = typeof GR_CHART_PIE_TEXTURES[number];
pieTextureForfunction
function pieTextureFor(index: number): GrChartPieTexture;
pieTexturePathsfunction

Штрихи текстуры для плитки со стороной tile. Диагональ дублируется у обоих углов и вылезает за плитку на единицу: без этого стык соседних плиток даёт разрыв штриха, и вместо ровной штриховки получается пунктир по сетке. Метка легенды рисует те же штрихи, только по своей стороне, — поэтому функция принимает размер, а не берёт его из константы.

function pieTexturePaths(texture: GrChartPieTexture, tile?: number): string[];

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

grChartPieSafelistconst
const grChartPieSafelist: string[];

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

GrChartPieSliceinterface

Круговая диаграмма: доли одного целого — сектором или кольцом. Живёт на общей раме, но система координат у неё полярная, и три вещи рама получает от круга: попадание указателя (угловое, а не по абсциссе), якорь тултипа (центроид доли) и строку скрытой таблицы (доля, а не значение по X). Осей и сетки у круга нет, вертикали под активной точкой — тоже.

interface GrChartPieSlice {
    label: string;
    /** `null`, ноль и отрицательное значение доли не дают: круг рисует только положительное. */
    value: number | null;
    /** Свой цвет вместо палитры. Заданный цвет отменяет текстуру: выбор потребителя сильнее. */
    color?: string;
    meta?: unknown;
}
GrChartPieActiveSliceinterface
interface GrChartPieActiveSlice {
    index: number;
    label: string;
    value: number | null;
    /** Доля от суммы, 0…1. `null` — доля не нарисована. */
    share: number | null;
    color: string;
}
GrChartPiePropsinterface
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';
}
GrChartPieEmitsinterface
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

Смены серии нет — серия одна. Активная доля вырастает наружу

КлавишаДействие
—доля

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

Отрицательных долей не бывает

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

Порядок долей — как пришли

Круг ничего не сортирует и не группирует. Хвост в «Прочее» собирает потребитель, потому что только он знает, что «Прочее» обязано стоять последним, а не попасть в середину по величине.

Своё число в середине бублика

По умолчанию в дырке стоит итог — сумма долей по valueFormat и подпись totalLabel. Слот #center заменяет его своим содержимым. Оно рисуется внутри холста, поэтому это SVG, а не HTML, и ставится от координат, которые слот отдаёт: cx/cy — центр бублика, innerRadius — радиус дырки, valueFont/labelFont — кегли содержимого по умолчанию, подобранные от её размера.

vue
<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.