GrChartPie

Package: @feugene/granularity-chartscompanionGroup: misc

Machine-translated from the Russian original, not yet reviewed. Read the original

When to take it

  • what one whole consists of — the structure of expenses, the shares of channels, the distribution of statuses: a circle shows a part of a whole and nothing more;
  • there are few shares — three to six; beyond that the segments become thinner than their labels, and they can no longer be compared by eye;
  • a total is needed in the centrevariant: 'donut' frees the middle for the sum or a key number (totalLabel, the #center slot);
  • the small stuff has to be taken off the picturelabelMinShare takes the labels of shares below the threshold away, leaving them in the legend and in the data table.

When to take something else

NeedTake
Compare quantities with one anotherGrChartBar
Show how the composition changed over timeGrChartArea with a stack
Show the share of a single metricGrProgressCircle
Compare the shape of a profile across several axesGrChartRadar

People cannot compare shares between two circles: an angle reads worse than a length, and two circles are of different sizes on top of that. Two compositions side by side are columns normalised to a hundred per cent.

There are no negative shares

Negative values and gaps are discarded rather than taken by absolute value: “minus three” of a part of a circle does not exist, and turning it into a plus silently would mean drawing data that is not there. What was discarded is visible by the sourceIndex of a share — it stays the index of the input.

The order of the shares is as they arrived

The circle sorts nothing and groups nothing. The tail is gathered into “Other” by the consumer, because only they know that “Other” has to stand last rather than end up in the middle by size.

Install

npm i @feugene/granularity-charts

Import

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

API

The API for this component has not been generated yet: the showcase generator only covers the core so far. Until it does, the reference lives in the package documentation.

Examples 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>

Accessibility

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

Full keyboard contract of the package

Component documentationAll components