Charts (recharts)
Gráficos transformam números em forma: uma tendência que sobe, uma fatia que
domina, um eixo onde uma série cruza a outra. O SDK embrulha o
recharts em cinco componentes temados — AreaChart,
BarChart, LineChart, PieChart e RadarChart — que recebem dados
tabulares simples (um array de objetos) e cuidam de eixos, grid, legenda,
tooltip e cores pra você.
Você não monta <XAxis>/<YAxis>/<Tooltip> na mão: passa data, diz qual
chave é o eixo (index) e quais chaves virar séries (categories), e o
componente faz o resto.
Por que um subpath separado
Os gráficos não vêm do barrel principal. Você os importa de
tempest-react-sdk/charts:
import { BarChart, LineChart, AreaChart } from "tempest-react-sdk/charts";
Por que isolar os charts num subpath?
O recharts é uma dependência pesada (D3 por baixo). A maioria dos apps
Tempest não desenha gráfico nenhum — e seria injusto cobrar esse peso de
todos. Por isso os charts moram num subpath dedicado e o recharts fica
externalizado no bundle do SDK. Apps que nunca importam de
tempest-react-sdk/charts não pagam nada: o tree-shaking do bundler do
app remove tudo.
Só quer a forma da série? Não precisa de chart
Um mini-gráfico inline — tendência numa célula de tabela, ao lado de um KPI —
é o Sparkline, que mora na entrada
raiz e é SVG puro. Nenhum recharts envolvido. Use os charts desta página
quando o leitor precisa ler valores no eixo.
Isso é o mesmo padrão do caller injeta a dependência pesada que o SDK já usa
nos adapters de telemetria (Sentry/PostHog) e feature flags
(GrowthBook/LaunchDarkly): o SDK descreve a integração, mas a biblioteca de
verdade fica por conta do app. A diferença é que aqui o recharts é uma peer
dependency opcional — você o instala uma vez e os cinco componentes o
reutilizam.
Instalação
npm i recharts
Sem o recharts, os charts não renderizam
Como o recharts é peer dep opcional, o npm install tempest-react-sdk
não o traz junto. Se você importar de tempest-react-sdk/charts sem ter
rodado npm i recharts, o build quebra com Cannot find module 'recharts'.
Instale-o no app que de fato usa gráficos.
A família cartesiana: Area, Bar, Line
AreaChart, BarChart e LineChart compartilham a mesma interface de
props, CartesianChartProps. Aprenda uma e você sabe as três — troca só o nome
do componente.
O modelo mental é sempre o mesmo:
data— suas linhas (array de objetos).index— a chave que vira o eixo X (rótulos: meses, dias, nomes…).categories— as chaves que viram séries (uma área/barra/linha cada).
BarChart
import { BarChart } from "tempest-react-sdk/charts";
const faturamento = [
{ mes: "Jan", receita: 12000, custo: 8000 },
{ mes: "Fev", receita: 15000, custo: 9000 },
{ mes: "Mar", receita: 18000, custo: 9500 },
{ mes: "Abr", receita: 21000, custo: 11000 },
];
export function FaturamentoMensal() {
return (
<BarChart
data={faturamento}
index="mes"
categories={["receita", "custo"]}
valueFormatter={(v) => `R$ ${v.toLocaleString("pt-BR")}`}
height={320}
/>
);
}
Duas séries (receita, custo), agrupadas lado a lado por mês. O
valueFormatter formata os números no tooltip e no eixo Y.
LineChart
Mesma forma de dados, mesmo index e categories — só muda o componente:
import { LineChart } from "tempest-react-sdk/charts";
const visitas = [
{ dia: "Seg", organico: 320, pago: 120 },
{ dia: "Ter", organico: 410, pago: 150 },
{ dia: "Qua", organico: 380, pago: 90 },
{ dia: "Qui", organico: 520, pago: 200 },
{ dia: "Sex", organico: 610, pago: 240 },
];
export function VisitasSemanais() {
return (
<LineChart
data={visitas}
index="dia"
categories={["organico", "pago"]}
valueFormatter={(v) => v.toLocaleString("pt-BR")}
/>
);
}
stack não empilha linhas
CartesianChartProps tem a prop stack por uniformidade, mas o LineChart
a ignora — linhas empilhadas raramente fazem sentido. Use stack no
AreaChart ou no BarChart, onde ele de fato empilha as séries num
stackId comum.
AreaChart (com stack)
import { AreaChart } from "tempest-react-sdk/charts";
const trafego = [
{ hora: "08h", desktop: 120, mobile: 80, tablet: 20 },
{ hora: "12h", desktop: 200, mobile: 160, tablet: 30 },
{ hora: "18h", desktop: 90, mobile: 240, tablet: 25 },
{ hora: "22h", desktop: 60, mobile: 300, tablet: 40 },
];
export function TrafegoPorDispositivo() {
return (
<AreaChart
data={trafego}
index="hora"
categories={["desktop", "mobile", "tablet"]}
stack
valueFormatter={(v) => `${v} sessões`}
/>
);
}
Com stack, as três áreas se empilham e o topo mostra o total por hora.
CartesianChartProps — referência
| Prop | Tipo | Default | O que faz |
|---|---|---|---|
data |
ChartData |
— | Linhas a plotar (array de objetos chave → string \| number). |
index |
string |
— | Chave da linha usada no eixo X (cartesiano) ou eixo angular (radar). |
categories |
string[] |
— | Chaves a plotar, uma série cada. |
colors |
string[] |
tokens --tempest-chart-* |
Cores das séries, cicladas por categoria. |
height |
number |
300 |
Altura do gráfico em pixels. |
width |
number |
— | Largura fixa em px. Quando definida, dispensa o ResponsiveContainer. |
stack |
boolean |
false |
Empilha as séries num stackId comum (ignorado pelo LineChart). |
showLegend |
boolean |
true |
Renderiza a legenda. |
showGrid |
boolean |
true |
Renderiza o grid cartesiano. |
showTooltip |
boolean |
true |
Renderiza o tooltip. |
valueFormatter |
(value: number) => string |
— | Formata valores numéricos no tooltip e no eixo Y. |
className |
string |
— | Classe extra aplicada ao wrapper do gráfico. |
ChartData = Array<Record<string, string | number>> — cada linha mapeia uma
chave de coluna a um rótulo (string) ou valor (number).
Uma série, ou várias
categories é um array, então você decide quantas séries quer. Uma só
(categories={["receita"]}) desenha um gráfico simples; várias desenham
séries comparativas, cada uma com a próxima cor da paleta.
PieChart
A PieChart tem uma forma de dados diferente: uma linha por fatia. Em vez de
categories, você diz qual chave segura o valor (category) e qual segura o
rótulo (index).
import { PieChart } from "tempest-react-sdk/charts";
const planos = [
{ plano: "Free", usuarios: 4200 },
{ plano: "Pro", usuarios: 1800 },
{ plano: "Business", usuarios: 600 },
{ plano: "Enterprise", usuarios: 120 },
];
export function DistribuicaoDePlanos() {
return (
<PieChart
data={planos}
category="usuarios"
index="plano"
donut
valueFormatter={(v) => `${v.toLocaleString("pt-BR")} usuários`}
/>
);
}
Cada linha vira uma fatia colorida pela próxima cor da paleta. Com donut, o
centro fica vazio (raio interno de 60%) — ótimo pra colocar um total no meio.
PieChartProps — referência
| Prop | Tipo | Default | O que faz |
|---|---|---|---|
data |
ChartData |
— | Linhas a plotar, uma fatia cada. |
category |
string |
— | Chave da linha com o valor numérico da fatia. |
index |
string |
— | Chave da linha com o nome/rótulo da fatia. |
colors |
string[] |
tokens --tempest-chart-* |
Cores das fatias, cicladas por fatia. |
height |
number |
300 |
Altura do gráfico em pixels. |
width |
number |
— | Largura fixa em px. Quando definida, dispensa o ResponsiveContainer. |
donut |
boolean |
false |
Renderiza como rosca (raio interno não-zero) em vez de pizza cheia. |
showLegend |
boolean |
true |
Renderiza a legenda. |
showTooltip |
boolean |
true |
Renderiza o tooltip. |
valueFormatter |
(value: number) => string |
— | Formata valores numéricos no tooltip. |
className |
string |
— | Classe extra aplicada ao wrapper. |
A PieChart não tem showGrid nem stack
Pizza não tem grid cartesiano nem empilhamento — essas props da família cartesiana simplesmente não existem aqui.
RadarChart
A RadarChart reusa CartesianChartProps (mesma assinatura de Area/Bar/Line),
mas plota polígonos num eixo radial: index vira o eixo angular (os vértices)
e cada entrada de categories vira um polígono.
import { RadarChart } from "tempest-react-sdk/charts";
const skills = [
{ atributo: "Velocidade", time_a: 80, time_b: 65 },
{ atributo: "Defesa", time_a: 70, time_b: 90 },
{ atributo: "Ataque", time_a: 95, time_b: 75 },
{ atributo: "Resistência", time_a: 60, time_b: 85 },
{ atributo: "Técnica", time_a: 88, time_b: 80 },
];
export function ComparativoDeTimes() {
return (
<RadarChart
data={skills}
index="atributo"
categories={["time_a", "time_b"]}
valueFormatter={(v) => `${v} pts`}
/>
);
}
Dois polígonos sobrepostos comparam time_a e time_b em cada atributo —
perfeito pra comparar perfis multidimensionais.
A RadarChart ignora showGrid e stack
O radar sempre desenha seu próprio PolarGrid (não há showGrid), e não
empilha séries (stack é ignorado). showLegend/showTooltip/colors/
valueFormatter funcionam normalmente.
Cores e tema
Você não precisa fazer nada: por padrão as séries saem dos tokens
--tempest-chart-1 … --tempest-chart-8 do tema. Trocar a marca com
createTheme({ chart: [...] }) move os gráficos junto, e virar o tema escuro
troca a paleta pela versão clareada — sem prop nenhuma no gráfico.
/* o que o SDK já define (colors.css) */
:root {
--tempest-chart-1: #2563eb; /* azul */
--tempest-chart-2: #16a34a; /* verde */
--tempest-chart-3: #f59e0b; /* âmbar */
--tempest-chart-4: #7c3aed; /* violeta */
--tempest-chart-5: #ec4899; /* rosa */
--tempest-chart-6: #06b6d4; /* ciano */
--tempest-chart-7: #ea580c;
--tempest-chart-8: #0f766e;
}
Uma paleta de 6 cores não deve virar 8
Se você define só chart-1..6, o leitor continuaria andando nos
--tempest-chart-7/-8 embutidos do SDK e o gráfico com 7 séries sairia com
paleta misturada — 6 cores da sua marca + 2 sobras. Por isso existe
--tempest-chart-count: o createTheme escreve quantas cores o tema tem, e o
resolveChartColors para ali. Definindo tokens à mão, declare junto:
:root {
--tempest-chart-1: #0f766e;
--tempest-chart-2: #f97316;
--tempest-chart-count: 2;
}
Sobrescreva no seu CSS, ou gere com o factory de tema:
import { applyTheme, createTheme } from "tempest-react-sdk";
applyTheme(createTheme({
primary: "#0f766e",
chart: ["#0f766e", "#f97316", "#9333ea"],
}));
colors={["var(--meu-token)"]} não funciona
O recharts aplica cor como atributo de apresentação do SVG
(fill="…"), e navegador nenhum substitui var() ali — custom property só
é resolvida em declaração CSS. Um var() passado em colors renderiza
como cor inválida (série invisível).
É por isso que o SDK lê os tokens via getComputedStyle e entrega cor
literal pro recharts. Se você precisa do valor de um token seu em JS, use o
mesmo caminho:
import { readThemeToken } from "tempest-react-sdk";
const marca = readThemeToken("--minha-marca"); // "#0f766e"
Para um gráfico específico, colors continua ganhando de tudo — é a via de
escape, ciclada por índice da série (ou fatia):
import { BarChart, DEFAULT_CHART_COLORS } from "tempest-react-sdk/charts";
export function VendasComCoresDaMarca() {
return (
<BarChart
data={vendas}
index="mes"
categories={["loja_a", "loja_b", "loja_c"]}
colors={["#0f766e", "#f97316", "#9333ea"]}
/>
);
}
// Ajustar só a primeira cor e manter o resto do fallback:
const minhaPaleta = ["#e11d48", ...DEFAULT_CHART_COLORS.slice(1)];
DEFAULT_CHART_COLORS é o fallback, usado quando os tokens não são
legíveis: sem styles.css importado, fora do browser (testes, script de build)
ou numa página que removeu os tokens.
Resolvendo tokens você mesmo
import { resolveChartColors, useChartColors } from "tempest-react-sdk/charts";
// dentro de um componente — re-resolve quando o tema virar
const colors = useChartColors();
// fora do React (canvas, export de imagem, tooltip customizado)
const palette = resolveChartColors();
useChartColors observa o atributo data-tempest-theme e re-resolve na troca de
tema; passar um array explícito curto-circuita o hook (nenhum observer é criado).
Precisa da cor do grid/eixo? resolveChartChrome("grid" | "axis").
Tema escopado numa seção
Os dois aceitam um elemento: useChartColors(undefined, sectionRef.current)
resolve os tokens daquela subárvore, então uma seção com tema próprio
pinta seus gráficos com a paleta dela.
Escala contínua: magnitude e polaridade
As 8 cores de série codificam identidade — qual série é qual. Um heatmap ou um choropleth codifica quanto, e isso é outro trabalho: precisa de um hue escalonado por claridade, não de oito hues.
import { sequentialScale, divergingScale, scaleSteps } from "tempest-react-sdk";
const cor = sequentialScale({ min: 0, max: 250 });
<rect fill={cor(valor)} />;
// Polaridade: variação contra a meta
const desvio = divergingScale({ min: 80, max: 130, center: 100 });
<rect fill={desvio(realizado)} />;
| Export | O que faz |
|---|---|
sequentialScale |
{ min, max, ordinal? } → (valor) => cor |
divergingScale |
{ min, max, center? } → (valor) => cor |
scaleSteps |
Todos os passos em ordem, pra montar a legenda |
SEQUENTIAL_STEP_COUNT |
7 |
DIVERGING_STEP_COUNT |
9 (1–4 frio · 5 neutro · 6–9 quente) |
ORDINAL_START_STEP |
3 — primeiro passo que passa 2:1 na superfície |
Sai da raiz, não do /charts
São matemática de token pura, sem recharts. Quem mais precisa delas — um
choropleth do /br, um heatmap feito à mão — não tem motivo pra instalar
recharts. Custo medido: 365 B brotli importando da raiz. O /charts
re-exporta só por descoberta.
Devolvem token, não hex
O retorno é var(--tempest-chart-sequential-4). Um heatmap pintado uma vez
segue o tema — inclusive o escuro, cujos passos são escolhidos pra superfície
escura, não invertidos do claro.
Sequencial deixa o zero recuar; ordinal não pode
Numa sequencial o passo mais claro some na superfície de propósito: é o que
"quase nada" deve parecer num heatmap. Numa ordinal — degrau de funil, faixa,
tier — cada passo é uma marca que alguém precisa ver, e um passo invisível é um
dado perdido. Passe ordinal: true e a escala começa no passo 3.
sequentialScale({ min: 0, max: 4, ordinal: true }); // usa 3..7
Cada braço da divergente escala pelo próprio alcance
Num domínio assimétrico (−5 a +80) os negativos ainda usam o braço frio inteiro. Escalar os dois braços pelo mais largo — o erro fácil — colapsaria todo negativo no passo ao lado do meio e esconderia o sinal.
O meio da divergente é cinza, nunca um hue
Um meio colorido lê como uma terceira categoria em vez de "sem desvio", que é a única coisa que uma divergente existe pra mostrar. Por isso o token 5 é neutro nos dois modos.
Escala contínua precisa de legenda
Sem uma faixa com rótulo nas pontas, ninguém converte cor de volta em número. O
scaleSteps existe pra isso:
<div style={{ display: "flex" }}>
{scaleSteps("sequential").map((cor) => (
<span key={cor} style={{ background: cor, width: 20, height: 10 }} />
))}
</div>
<span>0</span> … <span>250</span>
Como as rampas foram feitas
Não foram escolhidas a olho. Os passos são calculados em OKLCH com claridade espaçada por igual, então passo igual de dado parece passo igual de cor — o que não acontece espaçando em RGB. A croma segue um domo: as pontas ficam críveis e o meio carrega o hue.
Cada rampa foi validada por script nos dois modos: claridade monótona, gap ≥ 0,06
entre passos adjacentes, hue único, e a ponta perto da superfície passando 2:1 no
recorte ordinal. O createTheme refaz as duas escalas a partir do hue da marca
(usando o danger do tema como polo quente, pra "quente" e "ruim" não discordarem na
tela), então rebrandar move o heatmap junto em vez de deixá-lo azul do SDK.
Responsivo por padrão, fixo quando preciso
Por padrão, cada gráfico se estica pra largura do pai via um
ResponsiveContainer do recharts — você controla só a height. É o que você
quer em quase todo dashboard: a largura acompanha a coluna.
// Largura fluida (preenche o container), altura fixa de 300px (default).
<LineChart data={data} index="dia" categories={["valor"]} />
Mas há casos em que você precisa de uma largura fixa e determinística: testes
de snapshot, renderização no servidor (SSR), exportar um PNG de tamanho exato. Aí
você passa width:
// Largura fixa de 600px — sem ResponsiveContainer.
<LineChart data={data} index="dia" categories={["valor"]} width={600} height={300} />
width desliga o ResponsiveContainer
Quando você define width, o gráfico renderiza naquela largura exata e
não é embrulhado num ResponsiveContainer. Isso é intencional: o
ResponsiveContainer mede o pai no cliente e não funciona bem em SSR/jsdom,
onde não há layout calculado. Para uma página normal no navegador, omita
width e deixe ele preencher o pai.
Recap
- Importe os charts de
tempest-react-sdk/charts— subpath dedicado. Orechartsé peer dep opcional: rodenpm i rechartsno app que usa gráficos. Quem não importa de lá não paga o peso (mesmo padrão do "caller injeta a dep pesada" dos adapters de telemetria/flags). AreaChart,BarCharteLineChartcompartilhamCartesianChartProps:data+index(eixo X) +categories(séries).stackempilha em Area/Bar; oLineCharto ignora.PieChartusacategory(valor) +index(rótulo), uma linha por fatia, comdonutopcional.RadarChartreusaCartesianChartProps(index= eixo angular); ignorashowGrid/stack.DEFAULT_CHART_COLORSé a paleta padrão (6 cores); sobrescreva via a propcolors, cicladas por série/fatia.- Sem
width, o gráfico é responsivo (estica no pai viaResponsiveContainer, você controla aheight). Comwidth, ele renderiza num tamanho fixo semResponsiveContainer— útil pra testes/SSR.