GrChartPie

Пакет: @feugene/granularity-chartsспутникГруппа: Прочее

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

Когда брать

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

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

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

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

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

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

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

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

Установка

npm i @feugene/granularity-charts

Импорт

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

API

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

Примеры 3

Basic

Basic
<script setup lang="ts">
import { computed, ref } from 'vue'

// `GrChartPie` подставляется авто-импортом (`unplugin-vue-components`).

const sources = [
  { label: 'Поиск', value: 4210 },
  { label: 'Прямые заходы', value: 1980 },
  { label: 'Соцсети', value: 1240 },
  { label: 'Почта', value: 620 },
  { label: 'Партнёры', value: 310 },
]

const variant = ref<'donut' | 'pie'>('donut')
const active = ref<number | null>(null)

const total = sources.reduce((sum, item) => sum + item.value, 0)

const readout = computed(() => {
  const slice = active.value === null ? null : sources[active.value]

  if (!slice)
    return null

  return {
    label: slice.label,
    value: slice.value.toLocaleString('ru-RU'),
    share: (slice.value / total).toLocaleString('ru-RU', { style: 'percent', maximumFractionDigits: 0 }),
  }
})
</script>

<template>
  <div class="grid gap-3">
    <div class="flex flex-wrap items-baseline justify-between gap-4">
      <span class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
        Источники визитов, ноябрь
      </span>

      <GrSegmented
        v-model="variant"
        size="sm"
        :options="[{ value: 'donut', label: 'Кольцо' }, { value: 'pie', label: 'Круг' }]"
        aria-label="Вид диаграммы"
      />
    </div>

    <!-- Место под показания зарезервировано всегда: иначе строка прыгает на каждом наведении. -->
    <span class="min-h-6 text-[length:var(--gr-control-text-sm)]">
      <template v-if="readout">
        <span class="text-[var(--gr-muted-fg)]">{{ readout.label }}</span>
        <strong class="ml-2 [font-variant-numeric:tabular-nums]">{{ readout.value }}</strong>
        <span class="ml-1 text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums]">· {{ readout.share }}</span>
      </template>
      <span v-else class="text-[var(--gr-muted-fg)]">Наведите курсор или нажмите стрелку</span>
    </span>

    <GrChartPie
      v-model:active-index="active"
      :data="sources"
      :variant="variant"
      :height="260"
      total-label="визитов"
      aria-label="Источники визитов за ноябрь"
    />
  </div>
</template>

Labels

Labels
<script setup lang="ts">
/**
 * Подписи стоят снаружи кольца на выносках, а не на самих долях.
 *
 * Палитра серий насыщенная в обеих темах, и текст поверх неё не проходит AA ни
 * белым, ни тёмным. Снаружи контраст держит обычная роль фона — и подпись
 * читается одинаково в светлой и в тёмной теме.
 */
const budget = [
  { label: 'Зарплаты', value: 62 },
  { label: 'Инфраструктура', value: 18 },
  { label: 'Маркетинг', value: 11 },
  { label: 'Обучение', value: 6 },
  // Хвост собран в «Прочее» самим потребителем: круг ничего не сортирует и не
  // группирует — порядок долей его не касается.
  { label: 'Прочее', value: 3 },
]
</script>

<template>
  <div class="grid gap-3">
    <span class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
      Структура расходов, % бюджета
    </span>

    <GrChartPie
      :data="budget"
      :height="260"
      labels="share"
      :label-min-share="0.05"
      :show-legend="false"
      aria-label="Структура расходов по статьям"
    />

    <p class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
      Доля в 3 % осталась без подписи: ниже порога подпись перекрыла бы соседнюю.
      Её значение всё равно доступно — курсором, стрелками и в скрытой таблице данных.
    </p>
  </div>
</template>

Textures

Textures
<script setup lang="ts">
/**
 * Семь долей на пяти ролях палитры.
 *
 * Шестая доля повторяет цвет первой, седьмая — второй, и на круге соседние доли
 * стоят вплотную: повтор читается как «это одно и то же». Поэтому со второго
 * круга палитры к цвету добавляется штриховка — тот же принцип, по которому
 * линейный график меняет форму точки.
 */
const languages = [
  { label: 'TypeScript', value: 38 },
  { label: 'Vue SFC', value: 24 },
  { label: 'CSS', value: 12 },
  { label: 'Markdown', value: 9 },
  { label: 'JSON', value: 7 },
  { label: 'Shell', value: 6 },
  { label: 'YAML', value: 4 },
]
</script>

<template>
  <div class="grid gap-3">
    <span class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
      Состав репозитория, % строк
    </span>

    <GrChartPie
      :data="languages"
      variant="donut"
      :height="260"
      total-label="% всего"
      aria-label="Состав репозитория по языкам"
    />
  </div>
</template>

Доступность

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

Полный клавиатурный контракт пакета

Документация компонентаВсе компоненты