# ChartDonut

Rosca com o total no meio.

```tsx
<ChartDonut
  data={porNatureza}
  valueKey="total"
  nameKey="natureza"
  centerValue={compact(246700)}
  centerLabel="no mês"
/>
```

A pizza responde "qual é a maior fatia" e nada mais. A rosca responde a mesma
coisa e ainda usa o buraco para dizer o total, que é o número que a pessoa veio
buscar. Um painel que mostra a divisão sem mostrar o total obriga a somar de
cabeça.

## O anel

`thickness` é a espessura, em fração do raio: quanto mais fino o anel, maior o
buraco, e é ali que o total precisa caber. Em `1` ela fecha e vira pizza.

O número do meio fica preso à largura do buraco. Total comprido escapando por
cima do anel é o defeito clássico dessa peça.

**O miolo apaga enquanto o ponteiro lê uma fatia, e isso é de propósito.** A
dica que aparece já traz o número daquela fatia; manter o total no centro ao
mesmo tempo deixaria dois números na tela sem dizer qual é qual, e o de baixo
do dedo é o que a pessoa foi buscar. O centro volta sozinho quando o ponteiro
sai — não há prop para desligar, e nada se perde, porque o total está a um
movimento de distância.

Se você precisa dos dois ao mesmo tempo na tela, o lugar do total é fora da
peça: um `Stat` ao lado, ou o título do cartão.

## Movimento

Na primeira vez que aparece, a rosca varre do zero: as fatias saem juntas do
topo e cada uma se abre até o seu ângulo. Quando os dados mudam, cada fatia anda
do ângulo velho ao novo. Nos dois casos, com a duração e a curva dos tokens
(`--rc-duration-slow`, `--rc-ease`), pela mesma decisão do `ChartContainer`.
Com "reduzir movimento", a rosca nasce pronta e a troca é seca.

## As cores

Sem `config`, cada fatia pega uma cor da paleta do tema, na ordem. Com `config`,
vale a `color` que você escreveu ali.

O que não funciona é `var(--color-<nome>)`: essas variáveis são escritas pelo
`ChartContainer`, e a rosca desenha sozinha, fora dele.

## Quando não usar

Acima de seis fatias ela para de informar: as menores viram tiras finas e a
legenda vira uma lista que a pessoa lê em vez de olhar. Nesse caso, barra
deitada lê melhor, e ainda cabe o rótulo por extenso.

## No React Native

Traduz, em `@rivocode/ui-native/chart`, com as mesmas props: `valueKey`, `nameKey`, `config`, `thickness`, `legend`, `centerValue`, `centerLabel`. Duas mudanças de tipo: o miolo é `string` e não `ReactNode`, e o `format` só aceita função, que é a decisão que o `Meter` nativo já tinha tomado. Resolver nome de formatador arrasta o `Intl` inteiro para o bundle do celular.

**O que muda de verdade é como se lê uma fatia.** No web o ponteiro pousa no anel, a dica diz nome e valor, e o total sai de cena para os dois números não se empilharem. No toque não existe pousar, e o gesto equivalente mora na **legenda**, não na fatia: tocar a linha acende a fatia dela e manda nome e valor para o meio, no lugar exato onde o web põe a dica; tocar de novo devolve o total.

A fatia não é o alvo, e a razão é aritmética: um anel de 190px tem cerca de 600px de contorno para dividir entre até seis fatias, e a de 2% fica com doze (a mesma conta que tirou a dica por quadrado do `Tracker`). A linha da legenda tem 44px e a largura da tela.

**E a leitura de tela não usa o truque do `Tracker`.** Lá os 90 períodos viraram uma parada `adjustable` só, porque 90 paradas dentro de um cartão são um obstáculo. Aqui são no máximo seis fatias (acima disso a rosca para de informar e barra deitada lê melhor), e seis paradas com nome e valor são melhores que uma ajustável, porque cada uma é também o botão que acende a fatia. Contagem diferente, saída diferente. Com `legend={false}` o desenho vira imagem cujo nome carrega as fatias **e os valores**: sem legenda e sem dica, o dado ficaria inalcançável.

Uma diferença de desenho, e ela é medida: as pontas das fatias saem **retas**. O `cornerRadius` do web vem da Recharts, que recorta o canto de uma fatia preenchida; aqui a fatia é um arco traçado, e a ponta redonda que o SVG oferece estende o traço em quase doze graus para cada lado na espessura padrão: uma fatia de 5% apareceria como 11%.

O movimento é o do web: a rosca nasce pronta e, quando os dados mudam, cada fatia anda do ângulo velho ao novo com a duração e a curva dos tokens, pelo Reanimated. Com "reduzir movimento", a troca é seca.

## Importação

```tsx
import { ChartDonut } from '@rivocode/ui/chart'
```

## Exemplos

### Num cartão de painel

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@rivocode/ui'
import { ChartDonut, compact, currencyShort, type ChartConfig } from '@rivocode/ui/chart'

const BY_KIND = [
  { kind: 'servico', total: 148_200 },
  { kind: 'produto', total: 62_400 },
  { kind: 'locacao', total: 24_600 },
  { kind: 'frete', total: 11_500 },
]

const NATURE: ChartConfig = {
  servico: { label: 'Serviço' },
  produto: { label: 'Produto' },
  locacao: { label: 'Locação' },
  frete: { label: 'Frete' },
}

const TOTAL = BY_KIND.reduce((sum, row) => sum + row.total, 0)

export function InADashboard() {
  return (
    <Card className="w-80">
      <CardHeader>
        <CardTitle>Por natureza</CardTitle>
        <CardDescription>Agosto de 2026.</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartDonut
          data={BY_KIND}
          valueKey="total"
          nameKey="kind"
          config={NATURE}
          format={currencyShort}
          centerValue={compact(TOTAL)}
          centerLabel="no mês"
        />
      </CardContent>
    </Card>
  )
}
```

### Sem a lista embaixo

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@rivocode/ui'
import { ChartDonut, compact, currencyShort, type ChartConfig } from '@rivocode/ui/chart'

const BY_KIND = [
  { kind: 'servico', total: 148_200 },
  { kind: 'produto', total: 62_400 },
  { kind: 'locacao', total: 24_600 },
  { kind: 'frete', total: 11_500 },
]

const NATURE: ChartConfig = {
  servico: { label: 'Serviço' },
  produto: { label: 'Produto' },
  locacao: { label: 'Locação' },
  frete: { label: 'Frete' },
}

const TOTAL = BY_KIND.reduce((sum, row) => sum + row.total, 0)

export function WithoutLegend() {
  return (
    <div className="w-64">
      <ChartDonut
        data={BY_KIND}
        valueKey="total"
        nameKey="kind"
        config={NATURE}
        legend={false}
        centerValue={compact(TOTAL)}
        centerLabel="no mês"
      />
    </div>
  )
}
```

### Anel fino

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@rivocode/ui'
import { ChartDonut, compact, currencyShort, type ChartConfig } from '@rivocode/ui/chart'

const BY_KIND = [
  { kind: 'servico', total: 148_200 },
  { kind: 'produto', total: 62_400 },
  { kind: 'locacao', total: 24_600 },
  { kind: 'frete', total: 11_500 },
]

const NATURE: ChartConfig = {
  servico: { label: 'Serviço' },
  produto: { label: 'Produto' },
  locacao: { label: 'Locação' },
  frete: { label: 'Frete' },
}

export function ThinRing() {
  return (
    <div className="w-64">
      <ChartDonut
        data={BY_KIND}
        valueKey="total"
        nameKey="kind"
        config={NATURE}
        thickness={0.16}
        format={currencyShort}
        centerValue="4"
        centerLabel="naturezas"
      />
    </div>
  )
}
```

## Props

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `data` | `Slice[]` | sim | 0.4.0 |  |
| `nameKey` | `keyof Slice & string` | sim | 0.4.0 | De onde sai o nome de cada fatia. |
| `valueKey` | `keyof Slice & string` | sim | 0.4.0 | De onde sai o numero de cada fatia. |
| `centerLabel` | `ReactNode` |  | 0.4.0 | A linha pequena embaixo do numero. |
| `centerValue` | `ReactNode` |  | 0.4.0 | O numero grande no meio, que apaga enquanto o ponteiro le uma fatia. |
| `config` | `ChartConfig` |  | 0.4.0 |  |
| `format` | `Format` |  | 0.4.0 | Como o numero e escrito: nome de formatador da casa (`currencyShort`, `percent`, `integer`...) ou funcao propria. |
| `label` | `string` |  | - | O que o leitor de tela ouve no lugar do desenho. |
| `legend` | `boolean` |  | 0.4.0 | A lista de fatias embaixo, com nome e valor. |
| `thickness` | `number` |  | 0.4.0 | Espessura do anel, em fracao do raio. |

Além dessas: repassa `className`, `style`, `id` e os demais atributos do elemento raiz.

## Ver também

- [ChartContainer](/componentes/chart-container.md)
- [ChartRadial](/componentes/chart-radial.md)
- [Sparkline](/componentes/sparkline.md)
- [Convenções da biblioteca](/convencoes.md): Provider, tokens e as regras que valem para toda peça
- [Índice completo](/llms.txt)
