# ChartContainer

A moldura de todo gráfico, sobre a Recharts. Vive em `@rivocode/ui/chart`.

Ela publica uma variável de CSS por serie, com o nome da serie: `emitidas` no
`config` vira `var(--color-emitidas)`, então a linha, a barra e a dica falam do
mesmo jeito e trocar a cor e mexer num lugar só. Sem cor declarada, entra a
proxima da paleta de oito na ordem do `config`.

A Recharts não le classe do Tailwind e não conhece os nossos tokens, então a
ponte tem que ser por variável. Escrever a cor direta no `stroke` funciona até
o tema mudar.

A altura fica com quem usa, por classe: gráfico sem altura definida some,
porque o contentor mede o pai. Onde a moldura mede largura e nenhuma altura, ela
avisa no console em desenvolvimento em vez de entregar um cartão vazio. O aviso
espera o layout assentar antes de acusar, porque a caixa mede zero por um quadro
no caminho normal.

## Os quatro finais de uma consulta

Os mesmos do `DataTable`, e o `empty` é o mesmo objeto: `title`, `description`,
`action` e `icon`. A ação é fortemente recomendada: um gráfico que só diz
"sem dados" empurra para a pessoa o trabalho de adivinhar o que fazer.

```tsx
<ChartContainer
  config={config}
  className="h-64"
  isLoading={query.isLoading}
  isError={query.isError}
  onRetry={query.refetch}
  empty={{
    title: 'Nenhuma nota em março',
    description: 'O gráfico começa a desenhar assim que a primeira for emitida.',
    action: <Button size="sm">Emitir nota</Button>,
  }}
>
  <LineChart data={meses}>{/* ... */}</LineChart>
</ChartContainer>
```

**O erro diz o que falhou.** `errorTitle` e `errorMessage` são o par: num
painel de quatro gráficos, "Não foi possível carregar o gráfico" quatro vezes
não diz qual deles caiu, e num produto que não fala português não diz nada.
Sem eles, o texto padrão continua o de sempre. Os dois nomes são os mesmos do
`DataTable`, de propósito, e atravessam para o React Native com os mesmos
padrões. Só o tipo estreita para `string`, porque o título do `Alert` nativo é
um `Text`.

**O botão de nova tentativa também se traduz.** `retryLabel` (padrão "Tentar de
novo") nomeia o botão que executa o `onRetry`, com o mesmo nome e o mesmo padrão
nas quatro peças de consulta. Sem ele, o painel em inglês saía com o título
traduzido e o botão em português.

**A espera se anuncia em voz alta.** `aria-busy` num nó sem papel não é lido por
leitor de tela nenhum: ele descreve o estado de uma região, e só chega a quem já
está dentro dela. Quem esperava ouvia silêncio, e a chegada do dado, que troca a
tela inteira, também não dizia nada. As quatro irmãs publicam a mesma região viva
(`role="status" aria-live="polite"`, marcada com `data-rc-status`), que diz
"Carregando…" enquanto a consulta não volta e "Conteúdo carregado" quando ela
volta. Ela existe antes de o texto mudar e é o mesmo nó do primeiro ao último
estado: região que nasce já com o texto dentro não dispara anúncio nenhum.

A moldura já tinha uma região viva antes desta, e as duas convivem: a do ponto
ativo, que copia a dica quando a Recharts anda de ponto em ponto pelo teclado, e
a da espera. `data-rc-status` e `data-rc-active-point` separam uma da outra.

**A contagem de pontos sai do próprio gráfico.** A moldura lê o `data` do filho
da Recharts, então na forma acima não é preciso repeti-lo. Passe `data` aqui só
quando os pontos não morarem no filho direto (`<ScatterChart>` com o `data` no
`<Scatter>`) ou quando a série desenhada não for a que decide o vazio.

Antes disso o vazio exigia `empty` **e** `data`, e quem passava só o primeiro
nunca via o estado que tinha pedido: o gráfico desenhava eixos sobre o nada, sem
erro nenhum. Onde a moldura ainda não acha ponto para contar, ela avisa no
console em desenvolvimento em vez de calar.

As peças da Recharts que a biblioteca veste saem pelo mesmo import:
`LineChart`, `Line`, `BarChart`, `Bar`, `AreaChart`, `Area`, `PieChart`, `Pie`,
`Cell`, `XAxis`, `YAxis`, `CartesianGrid` e `ReferenceLine`.

## Gradiente de área

Área chapada compete com a linha que a delimita: a cor cheia embaixo pesa tanto
quanto o traço em cima, e num gráfico de duas séries a de trás some atrás da da
frente.

```tsx
function Faturamento() {
  return (
    <ChartContainer config={config} className="h-64">
      <AreaChart data={meses}>
        <ChartAreaGradient id="faturamento" series={['faturado']} />
        <Area
          dataKey="faturado"
          stroke="var(--color-faturado)"
          fill={areaGradient('faturamento', 'faturado')}
        />
      </AreaChart>
    </ChartContainer>
  )
}
```

O `id` do gradiente sai do `id` deste gráfico. Sem isso, dois gráficos na mesma
página com o mesmo nome de série pintariam um com o gradiente do outro, porque
`id` de SVG é global no documento.

## Movimento

A moldura cuida do movimento sozinha, e nenhuma marca precisa de prop para
isso. Toda `Line`, `Bar`, `Area`, `Pie`, `Radar`, `RadialBar` e `Scatter` que
ela embrulha sai com três props vestidas:

- **`animationDuration`** lido de `--rc-duration-slow`, e **`animationEasing`**
  lido de `--rc-ease`, no valor computado da própria moldura. A Recharts
  interpola em JavaScript e não enxerga variável de CSS; por isso a moldura lê o
  token depois de montar e entrega o número. O padrão da Recharts (1500 ms, `ease`)
  não aparece em lugar nenhum.
- **`isAnimationActive`** ligado antes de a marca montar, e desligado com
  "reduzir movimento": **na primeira vez que aparece com dados, o gráfico se
  desenha, e depois anda quando o dado muda.**

O segundo é decisão do dono, e substitui a anterior, de que o gráfico nascia
pronto. A barra cresce da base, a linha e a área se revelam da esquerda, a rosca
e o arco varrem do zero. O gráfico que chega depois do `isLoading`, do erro ou
do vazio também entra desenhando: o esqueleto era a espera, e o desenho é o dado
chegando. Depois disso, o que o movimento informa é a **mudança**: o filtro
trocou, o mês virou, e a barra que anda do valor velho ao novo mostra quanto
mudou. A duração é uma só, a de `--rc-duration-slow`, na entrada e na troca: a
Recharts reinicia a animação quando a duração muda, e trocar o número depois da
entrada faria o gráfico se desenhar duas vezes.

**No servidor o gráfico não desenha.** A Recharts só pinta depois de medir a
caixa, então o HTML do SSR sai com a moldura, a legenda e o anúncio, e sem o
SVG. A moldura lê os tokens num efeito de layout, antes de a medida chegar, e a
primeira marca que monta no cliente já monta animada: não existe um quadro com
o desenho pronto que depois some para crescer de novo. O `Alert` do erro e o
`EmptyState` do vazio entram pelo movimento deles.

Marca com `isAnimationActive={false}` fica parada: a moldura só liga o que
ninguém desligou. `animationDuration` e `animationEasing` escritos à mão
também vencem.

`useChartMotion()` continua exportado e devolve o mesmo trio, para quem desenha
com a Recharts **fora** da moldura:

```tsx
const motion = useChartMotion()

<Line dataKey="pagas" stroke="var(--color-pagas)" {...motion} />
```

Dentro do `ChartContainer` ele é dispensável, e espalhá-lo não muda nada: a
moldura veste a marca do mesmo jeito. Fora dela, a entrada depende de a marca
montar depois do primeiro efeito, e é o que acontece com o gráfico dentro de um
`ResponsiveContainer`, que só desenha depois de medir. Num gráfico de largura e
altura fixas, a marca monta no mesmo quadro do gancho, nasce pronta e só anda
na troca de dado.

## As peças da Recharts que saem daqui

`Area`, `AreaChart`, `Bar`, `BarChart`, `Line`, `LineChart`, `Pie`, `PieChart`,
`Cell`, `Scatter`, `ScatterChart`, `Radar`, `RadarChart`, `RadialBar`,
`RadialBarChart`, `PolarGrid`, `PolarAngleAxis`, `PolarRadiusAxis`,
`CartesianGrid`, `XAxis`, `YAxis`, `ZAxis`, `LabelList`, `Rectangle`,
`ReferenceLine` e `ReferenceArea`.

A lista é curada, e não um `export *`. O `Tooltip` e o `Legend` da Recharts
**não** saem por aqui: os nossos já embrulham os dois, e o nome colidiria com o
`Tooltip` do catálogo.

## Os eixos

`ChartXAxis` e `ChartYAxis` embrulham os da Recharts com a cor, a fonte e o
respiro do tema, e com o `format` da casa: `format="dayMonth"` no eixo do tempo,
`format="currencyShort"` no de valor. Sem eles, cada tela escreve o próprio
`tickFormatter` e um eixo lê diferente do outro: R$ 12.400 aqui, 12400 ali,
12,4k na terceira.

## No React Native

Traduz, no caminho próprio `@rivocode/ui-native/chart`, com o mesmo arranjo do formulário e pela mesma razão: o `react-native-svg` é peer **opcional**, e no celular ele não é só bytes, é módulo nativo que o app precisa ligar e reconstruir.

**O que atravessa inteiro são os quatro finais.** `isLoading`, `isError`, `onRetry`, `errorTitle`, `errorMessage`, `retryLabel`, `empty` e `data` têm os mesmos nomes e o mesmo sentido, e a espera desenha as mesmas seis barras desiguais. Três diferenças de tipo, todas porque texto no nativo mora dentro de um `Text`: `errorMessage`, `empty.title` e `empty.description` são `string`. O `empty.icon` atravessa, e aceita também a função do `EmptyState` nativo. O botão de tentar de novo fica **fora** do aviso: o `Alert` nativo tem título e corpo, e o corpo é uma linha de texto.

**O que muda é o desenho.** No web a moldura embrulha um gráfico da Recharts, que mede o pai sozinho e lê a cor de cada série em `var(--color-série)`. Aqui não há Recharts, não há contentor que meça e não há variável viva. Então a moldura mede com `onLayout`, resolve as cores do `config` e **entrega as duas coisas** a quem desenha, como o `Form` nativo entrega o `submit`:

```tsx
<ChartContainer config={SERIES} data={meses} className="h-56">
  {({ width, height, colors }) => (
    <Svg width={width} height={height}>…</Svg>
  )}
</ChartContainer>
```

O `colors` do quadro é um **mapa pela chave do `config`**, e não um array: é o `var(--color-série)` do web com outro veículo, e quem desenha pede a cor de `receita` pelo nome, que é o que sobrevive a alguém reordenar o `config`. O array é o `PALETTE`, e ele é array dos dois lados: é a ordem de sobra, de onde sai a cor de quem não declarou `color`. A diferença é que aqui ele é **exportado**, porque sem variável viva quem desenha à mão precisa alcançá-lo.

A medida chega **zerada no primeiro quadro** e verdadeira no seguinte: no telefone não existe largura antes do layout. O `children` também aceita JSX comum, e é assim que `ChartDonut` e `ChartRadial` ganham os quatro finais sem precisar de nada da moldura.

Duas regras a mais, as duas por causa do que não existe do lado de cá. O `config.color` pede **papel de token** (`chart-1` a `chart-8`), e não cor de CSS: a cor que a peça recebe é o valor final que vai para o desenho, e um hexadecimal escrito ali seria a única coisa da tela surda ao tema do cliente. E o `label` só vale na forma de função: com filho em JSX quem nomeia é a peça de dentro, e um `accessible` por cima dela fecharia a legenda da rosca numa parada só do leitor de tela.

**O movimento vem em duas marcas, porque aqui não há `Line` nem `Bar` para a moldura vestir.** `ChartBar` é a barra (`x`, `y`, `width`, `height`, `fill`, `radius`) e `ChartLine` é a linha (`points` em px, `stroke`, `strokeWidth`, `baseline`), as duas no mesmo caminho `/chart`. Na montagem elas entram (a barra cresce da base; a linha sobe da `baseline`, ou do ponto mais baixo) e, quando o valor muda, andam até o novo com a duração e a curva dos tokens (`duration-slow`, `ease`), pelo Reanimated sobre o `react-native-svg`: a mesma decisão do web, de que o gráfico se desenha ao aparecer e anda quando o dado muda. Com "reduzir movimento" elas nascem no lugar e saltam. A linha anda ponto a ponto quando a contagem é a mesma de antes, e troca de uma vez quando não é. Quem desenha com `Rect` e `Path` crus continua podendo, e fica parado.

## Importação

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

## Exemplos

### Linha

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function AsLine() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={INVOICES} className="h-64">
        <LineChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <ChartLegend content={<ChartLegendContent config={INVOICES} />} />
          <Line
            dataKey="emitidas"
            stroke="var(--color-emitidas)"
            strokeWidth={2}
            dot={false}
            activeDot={{ r: 4 }}
            isAnimationActive={false}
          />
          <Line
            dataKey="pagas"
            stroke="var(--color-pagas)"
            strokeWidth={2}
            dot={false}
            activeDot={{ r: 4 }}
            isAnimationActive={false}
          />
        </LineChart>
      </ChartContainer>
    </div>
  )
}
```

### Vazio, com saída

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const UMA: ChartConfig = { emitidas: { label: 'Emitidas' } }

export function EmptyWithAction() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer
        config={UMA}
        className="h-56"
        empty={{
          title: 'Nenhuma nota em março',
          description: 'O gráfico começa a desenhar assim que a primeira for emitida.',
          action: <Button size="sm">Emitir nota</Button>,
        }}
      >
        {/* O `data` vazio mora no filho, e a moldura o lê de lá: não é preciso
            repetir a mesma lista no `ChartContainer`. */}
        <LineChart data={[]}>
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <Line dataKey="emitidas" stroke="var(--color-emitidas)" isAnimationActive={false} />
        </LineChart>
      </ChartContainer>
    </div>
  )
}
```

### Erro, dizendo qual gráfico falhou

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const UMA: ChartConfig = { emitidas: { label: 'Emitidas' } }

export function ErrorWithTitle() {
  return (
    <div className="w-full max-w-lg">
      {/* Num painel de quatro gráficos, "Não foi possível carregar o gráfico"
          quatro vezes não diz qual deles falhou. */}
      <ChartContainer
        config={UMA}
        className="h-56"
        isError
        onRetry={() => {}}
        errorTitle="Não foi possível carregar o faturamento"
        errorMessage="A consulta expirou. Tente de novo em alguns minutos."
      >
        <LineChart data={MESES}>
          <Line dataKey="emitidas" stroke="var(--color-emitidas)" isAnimationActive={false} />
        </LineChart>
      </ChartContainer>
    </div>
  )
}
```

### Linha em degrau

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const UMA: ChartConfig = { emitidas: { label: 'Emitidas' } }

export function StepLine() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={UMA} className="h-56">
        <LineChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={UMA} />} />
          <Line
            type="step"
            dataKey="emitidas"
            stroke="var(--color-emitidas)"
            strokeWidth={2}
            dot={{ r: 3 }}
            isAnimationActive={false}
          />
        </LineChart>
      </ChartContainer>
    </div>
  )
}
```

### Área com gradiente

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function GradientArea() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={INVOICES} className="h-64">
        <AreaChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <ChartAreaGradient id="area" series={['emitidas', 'pagas']} />
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <ChartLegend content={<ChartLegendContent config={INVOICES} />} />
          <Area
            dataKey="emitidas"
            stroke="var(--color-emitidas)"
            fill={areaGradient('area', 'emitidas')}
            strokeWidth={2}
            isAnimationActive={false}
          />
          <Area
            dataKey="pagas"
            stroke="var(--color-pagas)"
            fill={areaGradient('area', 'pagas')}
            strokeWidth={2}
            isAnimationActive={false}
          />
        </AreaChart>
      </ChartContainer>
    </div>
  )
}
```

### Área empilhada

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function StackedArea() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={INVOICES} className="h-56">
        <AreaChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <ChartAreaGradient id="empilhada" series={['emitidas', 'pagas']} from={0.5} to={0.1} />
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <Area
            dataKey="pagas"
            stackId="notas"
            stroke="var(--color-pagas)"
            fill={areaGradient('empilhada', 'pagas')}
            isAnimationActive={false}
          />
          <Area
            dataKey="emitidas"
            stackId="notas"
            stroke="var(--color-emitidas)"
            fill={areaGradient('empilhada', 'emitidas')}
            isAnimationActive={false}
          />
        </AreaChart>
      </ChartContainer>
    </div>
  )
}
```

### Barra

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function Bars() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={INVOICES} className="h-64">
        <BarChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <ChartLegend content={<ChartLegendContent config={INVOICES} />} />
          <Bar dataKey="emitidas" fill="var(--color-emitidas)" radius={4} isAnimationActive={false} />
          <Bar dataKey="pagas" fill="var(--color-pagas)" radius={4} isAnimationActive={false} />
        </BarChart>
      </ChartContainer>
    </div>
  )
}
```

### Barra empilhada

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function StackedBars() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={INVOICES} className="h-56">
        <BarChart data={MESES} margin={{ left: 4, right: 8, top: 8 }}>
          <CartesianGrid vertical={false} />
          <ChartXAxis dataKey="mes" />
          <ChartYAxis format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <Bar dataKey="pagas" stackId="a" fill="var(--color-pagas)" isAnimationActive={false} />
          <Bar
            dataKey="emitidas"
            stackId="a"
            fill="var(--color-emitidas)"
            radius={[4, 4, 0, 0]}
            isAnimationActive={false}
          />
        </BarChart>
      </ChartContainer>
    </div>
  )
}
```

### Barra deitada, com rótulo

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const UMA: ChartConfig = { emitidas: { label: 'Emitidas' } }

export function HorizontalBars() {
  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={UMA} className="h-56">
        <BarChart data={MESES} layout="vertical" margin={{ left: 4, right: 32 }}>
          <CartesianGrid horizontal={false} />
          <ChartXAxis type="number" hide />
          <ChartYAxis type="category" dataKey="mes" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={UMA} />} />
          <Bar
            dataKey="emitidas"
            fill="var(--color-emitidas)"
            radius={[0, 4, 4, 0]}
            isAnimationActive={false}
          >
            {/* O rótulo na ponta dispensa o eixo de valor inteiro. */}
            <LabelList
              dataKey="emitidas"
              position="right"
              className="fill-fg-muted"
              fontSize={12}
            />
          </Bar>
        </BarChart>
      </ChartContainer>
    </div>
  )
}
```

### Radar

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function AsRadar() {
  return (
    <div className="w-full max-w-sm">
      <ChartContainer config={INVOICES} className="h-64">
        <RadarChart data={MESES}>
          {/* `fill="none"` nao e detalhe: sem ele a grade sai como um poligono
              cinza chapado, e as duas series desaparecem atras dela. */}
          <PolarGrid stroke="var(--rc-chart-grid)" fill="none" />
          <PolarAngleAxis dataKey="mes" tick={{ fill: 'var(--rc-fg-subtle)', fontSize: 12 }} />
          <ChartTooltip content={<ChartTooltipContent config={INVOICES} />} />
          <Radar
            dataKey="emitidas"
            stroke="var(--color-emitidas)"
            fill="var(--color-emitidas)"
            fillOpacity={0.25}
            strokeWidth={2}
            isAnimationActive={false}
          />
          <Radar
            dataKey="pagas"
            stroke="var(--color-pagas)"
            fill="var(--color-pagas)"
            fillOpacity={0.25}
            strokeWidth={2}
            isAnimationActive={false}
          />
        </RadarChart>
      </ChartContainer>
    </div>
  )
}
```

### Dispersão

```tsx
import { Button } from '@rivocode/ui'
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ChartAreaGradient,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  ChartXAxis,
  ChartYAxis,
  LabelList,
  Line,
  LineChart,
  PolarAngleAxis,
  PolarGrid,
  Radar,
  RadarChart,
  Scatter,
  ScatterChart,
  areaGradient,
  type ChartConfig,
} from '@rivocode/ui/chart'

const MESES = [
  { mes: 'Mar', emitidas: 38, pagas: 30 },
  { mes: 'Abr', emitidas: 45, pagas: 39 },
  { mes: 'Mai', emitidas: 41, pagas: 40 },
  { mes: 'Jun', emitidas: 52, pagas: 44 },
  { mes: 'Jul', emitidas: 58, pagas: 51 },
  { mes: 'Ago', emitidas: 63, pagas: 47 },
]

const UMA: ChartConfig = { emitidas: { label: 'Emitidas' } }

export function AsScatter() {
  const pontos = MESES.map((mes) => ({ x: mes.emitidas, y: mes.pagas, mes: mes.mes }))

  return (
    <div className="w-full max-w-lg">
      <ChartContainer config={UMA} className="h-56">
        <ScatterChart margin={{ left: 4, right: 8, top: 8 }}>
          <CartesianGrid />
          <ChartXAxis type="number" dataKey="x" name="Emitidas" format="integer" />
          <ChartYAxis type="number" dataKey="y" name="Pagas" format="integer" width={40} />
          <ChartTooltip content={<ChartTooltipContent config={UMA} />} />
          <Scatter data={pontos} fill="var(--rc-chart-1)" isAnimationActive={false} />
        </ScatterChart>
      </ChartContainer>
    </div>
  )
}
```

### Três séries

```tsx
import { ChartLegendContent, type ChartConfig } from '@rivocode/ui/chart'

const SITUACOES: ChartConfig = {
  pagas: { label: 'Pagas' },
  abertas: { label: 'Abertas' },
  vencidas: { label: 'Vencidas', color: 'var(--rc-danger)' },
}

export function ThreeSeries() {
  return (
    <ChartLegendContent
      config={SITUACOES}
      payload={
        [
          { dataKey: 'pagas', value: 'pagas', color: 'var(--rc-chart-1)' },
          { dataKey: 'abertas', value: 'abertas', color: 'var(--rc-chart-2)' },
          { dataKey: 'vencidas', value: 'vencidas', color: 'var(--rc-danger)' },
        ] as never
      }
    />
  )
}
```

### Com duas séries

```tsx
import { ChartTooltipContent, currencyShort, type ChartConfig } from '@rivocode/ui/chart'

const INVOICES: ChartConfig = {
  emitidas: { label: 'Emitidas' },
  pagas: { label: 'Pagas' },
}

export function TwoSeries() {
  return (
    <ChartTooltipContent
      active
      label="Agosto"
      config={INVOICES}
      payload={
        [
          { dataKey: 'emitidas', value: 63, color: 'var(--rc-chart-1)' },
          { dataKey: 'pagas', value: 47, color: 'var(--rc-chart-2)' },
        ] as never
      }
    />
  )
}
```

### Com valor formatado

```tsx
import { ChartTooltipContent, currencyShort, type ChartConfig } from '@rivocode/ui/chart'

export function WithFormattedValue() {
  return (
    <ChartTooltipContent
      active
      label="Junho"
      config={{ servico: { label: 'Serviço' } }}
      formatValue={currencyShort}
      payload={[{ dataKey: 'servico', value: 62000, color: 'var(--rc-chart-1)' }] as never}
    />
  )
}
```

## Props

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `config` | `ChartConfig` | sim | 0.4.0 |  |
| `data` | `readonly unknown[]` |  | 0.4.0 | Os pontos, para a moldura saber contar zero. |
| `empty` | `{ title: ReactNode; description: ReactNode; action?: ReactNode; icon?: ReactNode; }` |  | 0.4.0 | O que aparece quando a consulta volta sem nenhum ponto. |
| `errorMessage` | `ReactNode` |  | 0.4.0 |  |
| `errorTitle` | `ReactNode` |  | - | O titulo do aviso de erro. |
| `isError` | `boolean` |  | 0.4.0 |  |
| `isLoading` | `boolean` |  | 0.4.0 |  |
| `label` | `string` |  | - | O que o leitor de tela ouve no lugar do desenho. |
| `onRetry` | `(() => void)` |  | 0.4.0 | Sem isto, o erro nao oferece nova tentativa. |
| `retryLabel` | `ReactNode` |  | - | O nome do botao que executa o `onRetry`. |

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

## Partes

O componente se monta com as peças abaixo. Todas vêm de `@rivocode/ui/chart`.

### ChartLegendContent

A legenda, com o nome que o `config` deu a cada serie. Entra no `content` do
`ChartLegend`.

Sem ela a Recharts mostra a chave crua do dado, `qtd_emitidas` em vez de
"Emitidas": nome de campo não é texto de tela.

Na pizza todas as fatias dividem o mesmo `dataKey`, e quem separa uma da outra
e o `name`. A legenda olha os dois, nessa ordem.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `config` | `ChartConfig` |  | 0.4.0 |  |
| `hidden` | `readonly string[]` |  | 0.4.0 | As series escondidas agora. |
| `onToggle` | `((key: string) => void)` |  | 0.4.0 | Chamado com a chave da serie clicada. |
| `payload` | `readonly LegendPayload[]` |  | 0.4.0 |  |

### ChartTooltipContent

A dica que segue o ponteiro, vestida com os nossos tokens. Entra no `content`
do `ChartTooltip`.

Ela substitui a dica da Recharts inteira, em vez de pintar por cima: a original
sai com fundo branco e borda cinza escritos em estilo embutido, e não ha classe
que corrija estilo embutido, no tema escuro ela vira um retangulo branco no
meio do gráfico.

`formatValue` formata o número; use para dinheiro e porcentagem.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `active` | `boolean` |  | 0.4.0 | If true, then Tooltip is always displayed, once an activeIndex is set by mouse over, or programmatically. |
| `config` | `ChartConfig` |  | 0.4.0 |  |
| `formatValue` | `((value: number, key: string) => ReactNode)` |  | 0.4.0 | Formata o valor. |
| `hideIndicator` | `boolean` |  | 0.4.0 | Esconde a bolinha de cor de cada linha. |
| `label` | `string \| number` |  | 0.4.0 |  |
| `payload` | `TooltipPayload` |  | 0.4.0 |  |

## Ver também

- [ChartDonut](/componentes/chart-donut.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)
