storytelling-viz: Gráficos prontos para usar
O objetivo é um gráfico que pode ser colocado diretamente em uma apresentação, relatório ou periódico, não um gráfico de barras qualquer. Ele deve atender a três requisitos:
- Tipo de gráfico correto. O tipo de gráfico corresponde ao que está sendo comparado, o foco é claro, as anotações são diretas e não são necessárias legendas.
- Funciona com dados reais. Rótulos longos, valores negativos, dados ausentes, muitas categorias e valores atípicos são exibidos corretamente.
- Atende às especificações de saída. Tamanho, tamanho da fonte, espessura da linha, formato e incorporação de fontes estão de acordo com o padrão.
Este skill cuida apenas do gráfico. A seleção do tópico, análise e estrutura do relatório estão fora do escopo deste skill.
Fluxo de trabalho
- Determine o cenário. O cenário determina as convenções padrão de cores, notas de rodapé, números, etc. (consulte "Convenções dos dois cenários"):
- Relatório de negócios: gráficos de indicadores de negócios para a equipe de negócios ou gerência, independentemente da frequência e ocasião (relatórios semanais, mensais, painéis diários, atribuição de anomalias, páginas de dados de negócios para gerência / conselho / reunião estratégica). O critério é "o conteúdo são indicadores de negócios? O leitor tomará decisões de negócios com base nisso?", não "é um relatório semanal?".
- Geral / Publicação: documentação de análise ou pesquisa, resultados experimentais, páginas da web, figuras de artigos, etc.
- Se não tiver certeza, trate como relatório de negócios (convenções mais rigorosas, apenas uma linha de nota de rodapé a mais). O cenário de relatório de negócios deve primeiro ler
references/business.md, mesmo que o código de exemplo pareça suficiente.
- Selecione a função de acordo com "o que comparar". Consulte a tabela de seleção de funções abaixo; ambos os cenários usam a mesma tabela.
- Escolha a especificação de saída. Use
spec=para especificar (slide, documento, periódico...). - Gere o gráfico e verifique os resultados automáticos. Após a renderização,
figure()/save()imprimeQA … okou lista cada aviso; o.qado valor de retorno é a mesma lista de problemas (lista vazia = aprovado). Se houver avisos, corrija. Não entregue. - Abra o PNG e verifique com a lista de verificação. Use Read para abrir o PNG gerado e verifique item por item de acordo com a lista de verificação no final.
Início rápido
# /// script
# dependencies = ["plotnine>=0.15,<0.16", "pandas", "pillow", "pypdf"]
# ///
import sys; sys.path.insert(0, "<skill>/scripts")
from viz_common import figure, titled, set_lang, save
import charts as C # Funções de gráfico gerais
import biz as B # Funções de gráfico para relatórios de negócios (podem ser misturadas com C)
set_lang("en") # ou "zh" (padrão)
c = C.bar_rank(df, "channel", "gmv", highlight="POS", top_n=8, fmt="num")
figure([[(titled(c, title="POS é o único canal que encolheu"), 1)]],
spec="doc", name="fig1", outdir="out")
save(c, "fig2", spec="journal-1col", outdir="out") # Periódico, figura única: sem título
# Relatório de negócios: mesma figure(), mas deve incluir nota de rodapé com escopo
k = B.kpi_cards([dict(label="GMV (MTD)", value=16.5e6, prev=15.9e6, target=57e6, progress=0.29)])
figure([[(k, 1)]], spec="doc", size=(6.5, 2), name="kpi", outdir="out",
caption="Base: GMV de pedidos pagos (USD); MTD até 9 Out vs 1–9 Set.")
Selecionar função
Encontre a linha de acordo com o que está sendo comparado. C. = charts.py, B. = biz.py; as funções marcadas com ★ são projetadas para relatórios de negócios, mas podem ser usadas em qualquer cenário.
| O que comparar | Função | Pontos-chave |
|---|---|---|
| Ranking, composição em um ponto no tempo, valores positivos/negativos | C.bar_rank(df, cat, value, highlight=, top_n=, fmt=) | Ordenação automática; mais de top_n itens são agrupados como "Outros/Other"; valores escritos dentro ou fora da barra |
| Quem causou a mudança (escala + taxa de crescimento + contribuição por dimensão) | ★ B.contrib_table(df, dim, prev, cur, top_n=, growth_alert=) | Barras de dados de escala, ▲▼ taxa de crescimento, variação, contribuição para a variação total; linha de total no topo |
| Tendência: 1–N linhas juntas | C.line_trend(df, x, y, group=, highlight=, band=, ref=) | Anotação direta no final da linha; rótulos de mês em duas camadas automáticos para mensal; quebra de linha para dados ausentes; band= para faixa de confiança |
| Tendência: vários grupos separadamente | C.small_multiples(df, x, y, facet, highlight=) | Mesma escala y em todos os painéis; linhas de fundo cinza são outros grupos |
| Tendência: um indicador de negócios + comparação com o mesmo período | ★ B.trend(df, date, value, compare="month"/"year", events=, as_of=, period_end=) | Linha de comparação do período alinhada automaticamente; anota eventos; linha tracejada para período incompleto + estimativa |
| Progresso: acumulado no período vs meta | ★ B.cumulative_vs_target(df, date, value, target, start, end, as_of=, prior=) | Real acumulado, linha de progresso da meta, acumulado do período anterior, estimativa baseada na média diária |
| Real vs Meta (várias linhas) | ★ B.bullet(df, cat, actual, target, progress=) | Barra real + linha vertical da meta + linha tracejada de "deveria ter completado conforme progresso"; linhas com progresso atrasado ficam vermelhas |
| Real vs Intervalo (intervalo de previsão, faixa normal) | C.range_dot(df, cat, low, high, value, flag="below", bench=) | Linhas fora do intervalo usam cor de destaque; bench= especifica linha de referência |
| Dois valores por grupo (antes/depois, planejado/real) | C.dumbbell(df, cat, start, end, labels=, highlight=) | Ponto vazio = início, ponto cheio = fim; ordenado pelo fim; cada linha com taxa de variação |
| Dois períodos (início e fim), escalas de grupo muito diferentes | C.slope(df, cat, start, end, mode="change", groups=) | Quando mode="change", todas as linhas partem de 0%; linhas com início 0 são removidas e um WARNING é emitido |
| Ponte de aumento/redução: período anterior → contribuições → período atual | C.waterfall(df, cat, delta, start=, end_label=) | Aumento/redução usam cor neutra padrão + sinal de positivo/negativo; quando há significado de negócios para bom/ruim, passe up=PAL.good, down=PAL.bad |
| Decomposição de métricas (GMV = usuários × frequência × ticket médio) | ★ B.driver_tree(nodes) | Cada nó mostra valor atual, ▲▼ variação, contribuição (log share para multiplicação, variação para adição) |
| Composição: proporção de cada parte em vários grupos | C.stacked_100(df, cat, part, value, order=, focus_cat=) | Partes em ordem natural, gradiente de uma cor; legenda em chart.legend |
| Tabela bidimensional (grupo × tempo, grupo × grupo) | C.heatmap(df, x, y, value, fmt=) | Branco para uma cor, escala de cores com percentis 2%–98%; texto branco em células escuras |
| Escala + Taxa (número de solicitações e taxa de aprovação) | ★ B.bar_line_dual(df, x, bar, line, line_range=) | Único eixo duplo permitido: barra de escala (esquerda) + linha de taxa (direita), eixo da taxa começando em 0 ou faixa fixa |
| Distribuição | C.distribution(df, value, group=, kind="box"/"strip"/"hist") | Ordenado pela mediana; bigodes em 1.5×IQR; amostragem quando muitos pontos |
| Relação entre duas variáveis contínuas | C.scatter(df, x, y, label=, fit="linear", groups=, x_title=, y_title=) | OLS + faixa de confiança, mostra R²; apenas pontos destacados são rotulados; groups= com legenda em chart.legend |
| Estimativas com intervalo de confiança | C.point_range(df, cat, est, low, high, ref=0) | Gráfico de floresta; à direita escreve "estimativa [limite inferior, limite superior]" |
| Resumo de alguns números-chave (topo do relatório) | ★ B.kpi_cards([dict(label, value, prev, target, progress, spark, …), …]) | Números grandes, ▲▼ variação, taxa de atingimento vs progresso temporal, linha de tendência em miniatura; fica vermelho apenas se anômalo |
Detalhes dos parâmetros das funções ★ (campos de kpi_cards, estrutura de nós de driver_tree, events= / as_of= de trend, prior= de cumulative_vs_target, regras de anomalia, alinhamento de comparação do período) estão em references/business.md.
Como escolher quando duas linhas se encaixam:
B.contrib_tablevsC.waterfall: para ver simultaneamente escala, taxa de crescimento e proporção, ou mais de cerca de 8 dimensões → tabela; para explicar "como o período anterior se transformou no período atual passo a passo", com no máximo cerca de 8 itens → gráfico de cascata. Em páginas de atribuição, os dois geralmente ficam lado a lado.C.line_trendvsC.small_multiples: se houver uma linha a ser destacada, ou no máximo 4 linhas →line_trend(mais de 4 linhas, adicionehighlight=); para examinar a forma de cada grupo, com grupos de status semelhante →small_multiples. Em periódico de coluna única (3,5 pol), não cabem mais de 5 facetas; useline_trend+highlight=.- Dois valores por grupo, primeiro observe a escala de cada grupo: se forem semelhantes (máx / mín < cerca de 10 vezes) →
C.dumbbell. Quando a diferença for de uma ordem de grandeza ou mais, os grupos pequenos ficam indistinguíveis na escala comum; então, desenhe diretamente a taxa de variação: no máximo cerca de 8 grupos →C.slope(mode="change"); mais grupos → primeiro calcule a coluna da taxa de variação e useC.bar_rank(df, cat, "chg", fmt="pct"), escrevendo a escala absoluta no rótulo ou em uma tabela ao lado.
Parâmetros e valores de retorno comuns a todas as funções:
highlight=: nome de categoria, lista ou função. Os itens selecionados usam a cor de destaque, o restante fica cinza, e o nome da categoria também fica em negrito na mesma cor.fmt=:"num","pct","pp","int","raw", string de formato ou função;nd=controla o número de casas decimais.- Retorna um objeto de painel, que pode ser colocado diretamente em
figure()/save();chart + camada plotninepode ser sobreposto;chart.legendé uma legenda de palavra colorida em uma linha, etitled()a usará automaticamente.
Quando não houver função adequada, desenhe manualmente:
- Use
C.Frame(w, h, xr, yr, left=…, …)para reservar espaços em branco ao redor com base na largura real do texto (text_width); useC.axis_range(lo, hi)para o intervalo dos eixos. - Use
C.Canvas()comrect,seg,pt,text,line,bandpara desenhar elementos ecv.plot(fr)para gerar o gráfico. - Encapsule como
C.Chart(build), ondebuild(w, h)calcula o layout somente após o tamanho final ser determinado.
Não codifique coordenadas ou intervalos de eixos diretamente no código.
Convenções dos dois cenários
As funções em si desenham da mesma forma em ambos os cenários; as diferenças estão nessas convenções padrão. Não há parâmetro de "cenário" no código: as funções ★ já usam a aparência de relatório de negócios (▲▼, vermelho apenas para anomalias) e fazem figure() emitir um WARNING se faltar a nota de rodapé com escopo; os gráficos C. na mesma página devem receber parâmetros conforme a tabela abaixo (por exemplo, caption=, highlight=, cores up= / down=).
| Item | Geral / Publicação | Relatório de negócios |
|---|---|---|
| Aumento/redução | Use good / bad somente quando houver significado claro de bom/ruim, e sempre com sinal de positivo/negativo ou texto | Sempre ▲▼ + cor neutra, não use vermelho/verde para aumento/redução |
| Quando usar vermelho | Apenas para indicar "ruim" | Apenas quando for julgado como anômalo (atrasado em relação ao progresso / acima do limite / fora de ±2σ histórico); anomalia em direção boa apenas em negrito |
| Chamar atenção do leitor "olhe aqui" (ex.: canal que puxa o crescimento para baixo) | highlight= (cor de destaque laranja) | Mesmo à esquerda; laranja significa apenas "olhe aqui", não indica bom/ruim; se é anômalo ou não é expresso por ▲▼ e vermelho |
| Nota de rodapé com escopo | Recomendado | Obrigatório (caption=): numerador, denominador, moeda, janela de tempo, base de comparação; se faltar, figure() emite WARNING |
| Base de comparação | Conforme necessário para a análise | Padrão: comparação mensal, acumulado do mês atual vs mesmo período do mês anterior (mesmo número de dias), cumprimento de meta; métricas de tráfego diário são alinhadas por dia da semana |
| Período atual incompleto | Cor clara ou linha tracejada + "Até o dia X" | Mesmo à esquerda, e desenha a estimativa do período inteiro com base no progresso |
| Números | De acordo com os hábitos da área | Valores em K/M/B; variação de taxa em pp, taxa de crescimento em %; unidade escrita no título ou nota |
| Eixo duplo | Não usar | Permitido apenas "barra de escala + linha de taxa" (B.bar_line_dual) |
| Gráfico de pizza / rosca | Evitar ao máximo | Proibido |
| Especificações comuns | doc, web, journal-* | Apresentação PPT: slide; documentos Lark / Notion ou chat: doc (ilustração PNG); se o usuário não disser onde colocar, use doc |
Razões e contraexemplos em references/rules.md (parte de relatório de negócios são B1–B7).
Especificações de saída (spec=)
| spec | Tamanho (polegadas) | Tamanho da fonte | Formato padrão | Uso |
|---|---|---|---|---|
slide / slide-4x3 | 13,33×7,5 / 10×7,5 | 12 / 11,5 | SVG com contornos, SVG editável, PNG (150dpi) | Slides de apresentação |
web | 8×4,5 | 10,5 | SVG, PNG (200dpi) | Páginas web, e-mail |
doc | 6,5×4 | 9 | PDF, SVG, PNG (300dpi) | Uma figura no corpo do documento |
a4 / letter | Página inteira | 10 | PDF, SVG, PNG | Relatório de página inteira |
journal-1col / -1.5col / -2col | 3,5 / 5,35 / 7,2 de largura | 7–7,5 | PDF, SVG, PNG (600dpi) | Periódicos (89 / 136 / 183 mm) |
size=(w, h)pode substituir o tamanho padrão, por exemplo, figura mais alta para periódico, linha de cartões KPI mais baixa.- O tamanho da fonte, espessura da linha e tamanho dos pontos são dimensionados de acordo com a especificação; PDFs usam fontes incorporadas Tipo 42, atendendo aos requisitos de periódicos.
- Figuras de periódico não têm título; a legenda é escrita no texto: figura única use
save(chart, name, spec="journal-1col"); figura com vários painéis usefigure(rows, spec="journal-2col"), sem passartitle=, numere os painéis comtitled(chart, title="a"). figure()gera uma página inteira (pode ter título, subtítulo, nota de rodapé com escopo, vários painéis);save(chart, "fig2", spec="journal-1col")exporta apenas uma figura, sem título de página e nota de rodapé. Relatórios de negócios precisam de nota de rodapé, então usefigure().- Dois tipos de SVG:
<name>.svgtem o texto convertido em contornos, fica igual em qualquer lugar;<name>_editable.svgmantém o texto, pode ser editado no Figma / PPT, mas o computador de destino precisa ter a mesma fonte. No PPT, use o segundo se precisar editar texto, caso contrário, use o primeiro ou PNG. slide/webnão geram PDF por padrão; passeformats=("pdf", "png")quando necessário.
Combinar vários painéis
figure(rows, title=, subtitle=, caption=, spec=):rows=[[(panel, span), …], …],spané a largura relativa dentro da mesma linha.- A altura da linha é distribuída automaticamente por padrão: linhas que contêm apenas texto ou tabelas têm altura conforme o conteúdo, as linhas restantes dividem o espaço restante igualmente.
titled(chart, title=, legend=, note=): adiciona título do painel, linha de legenda e nota acima do gráfico; selegend=não for fornecido, usachart.legendautomaticamente.- O título suporta marcação rich text:
**negrito**;[frase](focus)indica negrito e colorido. As cores podem serfocus,brand,good,bad,muted,inkou#hex. - Outros painéis:
text_panel(para(...))para parágrafos,table_panel(rows, header=)para tabelas pequenas (colunas numéricas alinhadas automaticamente à direita),column(a, b)empilha dois painéis verticalmente. - Cada painel é renderizado separadamente no tamanho final, portanto, o tamanho da fonte é o valor real em pontos; ao concatenar SVGs, os IDs dos elementos de cada painel recebem automaticamente prefixos para evitar conflitos.
Regras visuais (as funções já aplicam por padrão; ao desenhar manualmente, siga-as)
- Camadas de cor:
focus(laranja) destaca a única coisa a ser observada, não representa bom/ruim;brand(azul escuro) para séries regulares; cinza para fundo e referência; o uso degood/badsegue o cenário (consulte a tabela acima). - Mesma cor = mesmo significado. Palavras-chave e dados correspondentes usam a mesma cor; elementos com significados diferentes não compartilham a mesma cor.
- O texto deve ser legível. O contraste do texto deve ser de pelo menos 4,5:1. Ao usar cor de destaque para texto, use
readable(color)para escurecer automaticamente; texto em fundo escuro useink_on(fill). As funções tratam automaticamente; ao desenhar manualmente,Canvas.texttambém verificará e emitirá avisos. - Use anotações diretas em vez de legendas. O título do eixo não pode ser um nome de campo. Se todas as barras tiverem valores anotados, não são necessários eixo y e grade.
- A ordenação deve fazer sentido. Se houver ordem natural (estágio, nível), siga-a; caso contrário, ordene pelo valor; coloque o item de destaque na posição mais visível; não ordene alfabeticamente.
- Eixos. Gráficos de barras devem começar em 0; gráficos de linha, quando os dados estão próximos de 0, começam em 0 (
zero="auto"); painéis relacionados usam a mesma escala; eixo duplo apenas para "barra de escala + linha de taxa". - Precisão. Suficiente para distinguir. Todos os números são calculados a partir dos dados, formatados com
pct/pp/num/fmt_value, nunca escritos manualmente. - Escolha o gráfico de acordo com o que está sendo comparado: não use barras empilhadas de valor absoluto para mudanças de proporção; não desenhe vários gráficos de pizza para composição de vários grupos; quando houver mais de 4 linhas, use
highlight=para destacar ou mude para pequenos múltiplos; se comparar dois itens em um gráfico de árvore ou radar levar mais de cerca de 2 segundos, mude para barras horizontais. - Problemas com dados devem ser marcados no gráfico. Período atual incompleto use cor clara ou linha tracejada e indique "Até o dia X"; amostras pequenas escreva n; dados ausentes quebrem a linha, não conecte.
Detalhes e razões em references/rules.md; exemplos de redesign do SWD em references/swd_practices.md.
Idioma e tipografia
set_lang("zh" | "en")altera simultaneamente a fonte padrão, rótulos de mês (chinês "1月\n2026", inglês "Jan\n2026") e "Outros/Other". Unidades monetárias em ambos os idiomas usam K/M/B por padrão; para usar 万/亿, useset_number_style("cn"). Rótulos que contêm chinês usam automaticamente fonte CJK.T("其他", "Other")obtém a string conforme o idioma atual.- Strings em chinês passam automaticamente por
zh(): pontuação é convertida para largura total, espaços extras ao lado de pontuação de largura total são removidos, URLs e abreviações permanecem inalterados. - Regras de quebra de linha: números e palavras em inglês não são separados; pontuação não aparece no início da linha; parêntese de abertura não fica no final da linha.
- Na inicialização, a fonte usada é exibida. Se aparecer
WARNING: no CJK font, é necessário instalar o Noto Sans SC.
Verificação antes da entrega
Verificação automática (veja a saída de figure() ou .qa):
-
QA … ok: texto sem sobreposição, não saindo do painel, não cortado pela área de plotagem. - Sem avisos de contraste, nem avisos como
rows do not fit,labels take most of the panel,cells too small,basis caption.
Verificação manual (abra o PNG):
- Cobrindo o título e olhando apenas para o gráfico, consegue-se dizer o que ele expressa? Há apenas um foco?
- Os números no título e nas anotações vêm da mesma variável que os valores no gráfico?
- O tipo de gráfico corresponde ao que está sendo comparado (consulte a tabela de seleção de funções)? A ordenação faz sentido? Painéis relacionados têm a mesma escala?
- O uso de cores segue as convenções do cenário? Removendo as cores, bom/ruim / aumento/redução ainda podem ser lidos a partir de símbolos ou texto?
- Não há legendas padrão, nomes de campos, efeitos 3D ou gradientes?
- O tamanho e o tamanho da fonte estão de acordo com a especificação de destino (periódico ~7pt, slides ≥12pt)? Os PDFs necessários já foram exportados?
Relatório de negócios adicional:
- A nota de rodapé com escopo está completa? Os números foram conferidos com outros relatórios (no mesmo relatório, o mesmo número é consistente em todas as páginas)?
- Unidade, casas decimais e formato são uniformes em todo o gráfico?
Ambiente e testes
- Dependências:
plotnine>=0.15,<0.16(verificado em 0.15.8 + matplotlib 3.11),pandas,pillow, epypdfpara exportar PDF. O código está emscripts/viz_common.py(ferramentas base, especificações, exportação, QA),scripts/charts.py(C.) escripts/biz.py(B.). - Local de saída: Defina
outdirpara um diretório visível pelo usuário; não escreva dentro do diretório do skill. - Galeria de exemplos:
uv run references/gallery.py [outdir](cada funçãoC., slide em chinês + periódico em inglês);uv run references/business_gallery.py [outdir](cada funçãoB., slide em chinês + documento em inglês). A saída padrão éout/no diretório atual. - Testes de regressão:
uv run references/tests/edge_cases.pyusa 38 conjuntos de dados de borda, em duas especificações (total de 76 itens) para verificar o desempenho das funções e se o QA está conforme o esperado; após alterar funções de gráfico, deve ser executado novamente e todos os testes devem passar antes de publicar. - Quando plotnine não puder ser instalado no sandbox: Primeiro use o simulador matplotlib em
PYTHONPATH=<skill>/scripts/plotnine_previewpara visualizar o layout e executar QA (regras de largura de linha e corte são consistentes com plotnine). OQA okdo simulador não é critério de entrega: passe o script para o usuário executar comuv runem sua máquina e informe claramente que este gráfico ainda não foi renderizado e verificado com plotnine real.